﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;

using Noris.Schedule.Support;
using Noris.Schedule.Support.Services;
using Noris.Schedule.UI.Forms;
using Noris.Schedule.Support.Core;
using Noris.Schedule.Support.Data;

namespace Noris.Schedule.UI.Components
{
	#region CLASS GraphMultiplexCls : MULTIPLEX GRAFŮ + CLASS GraphMultiplexItemCls : položka multiplexu
	/// <summary>
	/// Třída, která implementuje multiplexor grafů v jednom panelu.
	/// Zajišťuje virtualizaci grafu z pohledu hostitele (panel se záložkami pro více grafů)
	/// a hostování konkrétního grafu v panelu, jeho napojení na data panelu.
	/// </summary>
	internal class GraphMultiplexCls : IDisposable
	{
		#region KONSTRUKCE
		/// <summary>
		/// Default konstruktor
		/// </summary>
		internal GraphMultiplexCls(GridPanelCls parent)
		{
            this._Parent = parent;
            this.PanelLayout = new GridPanelLayout();
            this._GraphItemDict = new Dictionary<int, GraphMultiplexItemCls>();
			this._DataSourceList = new List<IDataSource>();
			this._AddToItemId = -1;
			this._CurrentItemId = -1;
			this._GraphA = null;
			this._GraphB = null;
		}
		public void Dispose()
		{
			if (this._GraphItemDict != null)
				this._GraphItemDict.Clear();
			this._GraphItemDict = null;

			if (this._DataSourceList != null)
				this._DataSourceList.Clear();
			this._DataSourceList = null;
		}
        /// <summary>
        /// Parent objekt, jemu lze předávat události přímo
        /// </summary>
        private GridPanelCls _Parent;
		/// <summary>Data jednotlivých stránek multiplexoru</summary>
		private Dictionary<int, GraphMultiplexItemCls> _GraphItemDict;
		/// <summary>Souhrn instancí datových zdrojů. Každá instance je zde jen jednou, ale pokud je více instancí téže třídy, pak je zde každá.</summary>
		private List<IDataSource> _DataSourceList;
		/// <summary>ID multiplexu, co něhož se aktuálně přidávají grafy. 0 = ještě se nezačalo. První item bude mít ID = 1</summary>
		private int _AddToItemId;
		/// <summary>ID aktuálního multiplexu, index base 0. Setování hodnoty ji vepisuje i do Layoutu.</summary>
        private int _CurrentItemId
        {
            get { return this.__CurrentItemId; }
            set
            {
                this.PanelLayout.CurrentPageIndex = value;
                this.__CurrentItemId = value;
            }
        }
        private int __CurrentItemId;
		/// <summary>Příznak, že požadovaný multiplex byl nalezen a zobrazen</summary>
		internal bool CurrentItemFound;
		/// <summary>Reference na právě aktivní graf (ten, který má nebo posledně měl focus)</summary>
		private DColGrid _GraphCurrent;
		/// <summary>Referennce na horní graf (který je připevněn ke splitteru H, pod hlavičkou)</summary>
		private DColGrid _GraphA;
		/// <summary>Reference na dolní graf (nemusí být použit, pak je null)</summary>
		private DColGrid _GraphB;
		#endregion
		#region VYHLEDÁNÍ EXTERNÍCH DATOVÝCH ZDROJŮ
		/// <summary>
		/// Metoda najde a vrátí datový zdroj daného typu. Pokud neexistuje, vyhodí výjimku SysException.
		/// Metoda odpovídá předpisu delegáta FindDataSource.
		/// </summary>
		/// <param name="dataSourceType">Typ datového zdroje</param>
		/// <returns>Datový zdroj</returns>
		internal IDataSource GetDataSource(Type dataSourceType)
		{
			IDataSource ds = this.TryGetDataSource(dataSourceType);
			if (ds == null)
				Throw.SysError(MessageInfo.Get("Požadovaný datový zdroj %0 neexistuje.", dataSourceType.Namespace + "." + dataSourceType.Name));
			return ds;
		}
		/// <summary>
		/// Metoda najde a vrátí datový zdroj daného typu. Pokud neexistuje, vrátí null.
		/// Metoda odpovídá předpisu delegáta FindDataSource.
		/// </summary>
		/// <param name="dataSourceType">Typ datového zdroje</param>
		/// <returns>Datový zdroj</returns>
		internal IDataSource TryGetDataSource(Type dataSourceType)
		{
			IDataSource findDs = this._DataSourceList.Find(delegate(IDataSource ds) { return (ds.GetType() == dataSourceType); });
			return findDs;
		}
		#endregion
		#region PROPERTY A EVENTY
		/// <summary>Reference na právě aktivní list = dva grafy + splitter</summary>
		internal GraphMultiplexItemCls CurrentItem { get; private set; }
		/// <summary>Reference na právě aktivní graf (ten, který má nebo posledně měl focus)</summary>
		internal DColGrid GraphCurrent { get { return (this._GraphCurrent); } }
		/// <summary>Reference na horní graf (který je připevněn ke splitteru H, pod hlavičkou)</summary>
		internal DColGrid GraphA { get { return (this._GraphA); } }
		/// <summary>Referece na dolní graf (nemusí být použit, pak je null)</summary>
		internal DColGrid GraphB { get { return (this._GraphB); } }
		/// <summary>
		/// Vrátí graf odpovídající dané části (A/B) na aktuální záložce (
		/// </summary>
		/// <param name="part"></param>
		/// <returns></returns>
		internal DColGrid GetGraph(GraphMultiplexPart part)
		{
			switch (part)
			{
				case GraphMultiplexPart.PartA:
					return this.GraphA;
				case GraphMultiplexPart.PartB:
					return this.GraphB;
			}
			return null;
		}
        /// <summary>
        /// Příznak, že multiplex je prázdný (nemá žádnou záložku ani graf ani datový zdroj).
        /// </summary>
        internal bool IsEmpty { get { return (this._DataSourceList.Count == 0 && this._GraphItemDict.Count == 0); } }
		/// <summary>Příznak, že nyní existuje alespoň jeden graf</summary>
		internal bool IsGraphAny { get { return (_GraphA != null || _GraphB != null); } }
		/// <summary>Příznak, že nyní existuje pouze graf A</summary>
		internal bool IsOnlyGraphA { get { return (_GraphA != null && _GraphB == null); } }
		/// <summary>Příznak, že nyní existuje pouze graf B</summary>
		internal bool IsOnlyGraphB { get { return (_GraphA == null && _GraphB != null); } }
		/// <summary>Příznak, že nyní existují oba dva grafy (plus SplitterGrid)</summary>
		internal bool IsGraphAB { get { return (_GraphA != null && _GraphB != null); } }
		/// <summary>Souhrn textů, které definují texty záložek grafů</summary>
		internal string TabCaption { get { return this._GetTabCaption(); } }
		/// <summary>GID prvního z řádků</summary>
		internal GID FirstGId { get { return this._GetFirstGId(); } }
        /// <summary>
        /// Instance / reference objektu, který poskytuje informace o Columns pro Header.
        /// Může být sdílen skrz nadřízený prvek i do Gridu.
        /// </summary>
        internal GridPanelLayout PanelLayout { get; set; }
		/// <summary>
		/// Event, který vyvolá multiplexer poté, kdy některý graf vyžaduje své zobrazení, ale nenachází se na viditelné záložce (položce multiplexu).
		/// Vyšší (vizuální) vrstva by měla překreslit aktivní stránku (záložku). Může/nemusí volat this.ItemActivate().
		/// </summary>
		internal event MultiplexPageChangeEventHandler ExecPageActivate;
		/// <summary>
		/// Event, který signalizuje, že právě proběhla změna stránky grafu.
		/// Tento event nemá volat this.ItemActivate().
		/// </summary>
		internal event MultiplexPageChangeEventHandler PageCurrentChange;
		/// <summary>
		/// Event, který vyvolá multiplexer poté, kdy dojde k aktivaci jiného grafu, než který byl aktivní před tím.
		/// Aktivní graf je referencován v property this.GraphCurrent, a v době volání tohoto eventu je nastaven na aktivní graf (tj. nikdy není null).
		/// </summary>
		internal event MultiplexPageChangeEventHandler GraphCurrentChange;
		/// <summary>
		/// Invoker eventu ExecPageActivate
		/// </summary>
		protected void OnExecPageActivate(int itemId)
		{
			if (this.ExecPageActivate != null)
				this.ExecPageActivate(this, new MultiplexPageChangeEventArgs(itemId, null, null));
		}
		/// <summary>
		/// Invoker eventu PageCurrentChange
		/// </summary>
		/// <param name="itemId"></param>
		protected void OnPageCurrentChange(int itemId)
		{
			if (this.PageCurrentChange != null)
				this.PageCurrentChange(this, new MultiplexPageChangeEventArgs(this.CurrentItem.ItemID, this.CurrentItem, null));
		}
		/// <summary>
		/// Invoker eventu GraphCurrentChange
		/// </summary>
		protected void OnGraphCurrentChange()
		{
			if (this.GraphCurrentChange != null)
				this.GraphCurrentChange(this, new MultiplexPageChangeEventArgs(this.CurrentItem.ItemID, this.CurrentItem, this.GraphCurrent));
		}
		#endregion
		#region INICIACE (plnění položek, registrace háčku datového zdroje)
        /// <summary>
        /// Vynuluje svoje pole záložek a grafů a datových zdrojů
        /// </summary>
        internal void Clear()
        {
            foreach (GraphMultiplexItemCls multiplexItem in this._GraphItemDict.Values)
            {   // Odvázat z grafů naše event handlery:
                if (multiplexItem.ContainGraphA) _GraphEventsRemove(multiplexItem.GraphA);
                if (multiplexItem.ContainGraphB) _GraphEventsRemove(multiplexItem.GraphB);
                multiplexItem.GraphA = null;
                multiplexItem.GraphB = null;
            }
            foreach (IDataSource dataSource in this._DataSourceList)
            {   // Odvázat z datových zdrojů naši metodu (byla tam metoda this.DataSourceRequestCompleted) (pozor, toto není event, ale delegate):
                if (dataSource.RequestCompleted != null)
                    dataSource.RequestCompleted = null;
            }
            this.PanelLayout.Clear();
            this._GraphItemDict.Clear();
            this._DataSourceList.Clear();
            this._AddToItemId = -1;
            this._CurrentItemId = -1;
            this._GraphA = null;
            this._GraphB = null;
            this.PanelLayout.Clear();
        }
        /// <summary>
		/// Přidá do sebe další graf. Přidá jej podle potřeby: nahoru, na celou stránku, dolů.
		/// </summary>
		/// <param name="graph"></param>
        internal void AddGraph(DColGrid graph)
		{
			if (graph == null) return;

			if (this._AddToItemId < 0) this._AddToItemId = 0;
			GraphMultiplexItemCls multiplexItem;
			if (!this._GraphItemDict.TryGetValue(this._AddToItemId, out multiplexItem))
			{
				multiplexItem = new GraphMultiplexItemCls(this._AddToItemId);
				this._GraphItemDict.Add(this._AddToItemId, multiplexItem);
			}

			// Tady se rozhodnu, zda přidám daný graf do aktuální stránky, anebo zda založím novou stránku:
			bool newItem = false;
			GraphPositionType graphPosition = graph.GraphDeclarationData.GraphPosition;
			if (graphPosition == GraphPositionType.FullPage && (multiplexItem.ContainGraphA || multiplexItem.ContainGraphB))
				newItem = true;
			else if (graphPosition == GraphPositionType.TopPart && multiplexItem.ContainGraphA)
				newItem = true;
			else if (graphPosition == GraphPositionType.BottomPart && !multiplexItem.IsEmptyB)
				newItem = true;

			// Nová položka (záložka):
			if (newItem)
			{
				this._AddToItemId++;
				multiplexItem = new GraphMultiplexItemCls(this._AddToItemId);
				this._GraphItemDict.Add(this._AddToItemId, multiplexItem);
			}

			// Sestavuji si seznam instancí datových zdrojů (jedna instance pouze jedenkrát, i když by byla na více grafech)
			if (graph.GraphDeclarationData.DataSource != null && !this._DataSourceList.Exists(delegate(IDataSource ds)
				{ return Object.ReferenceEquals(ds, graph.GraphDeclarationData.DataSource); }))
				this._DataSourceList.Add(graph.GraphDeclarationData.DataSource);

			// Do datového zdroje grafu zaháčkuji svého delegáta - pro přebírání asynchronních výsledků poté, kdy datový zdroj dokončí svoji operaci:
			if (graph.GraphDeclarationData.DataSource != null && graph.GraphDeclarationData.DataSource.RequestCompleted == null)
				graph.GraphDeclarationData.DataSource.RequestCompleted = this.DataSourceRequestCompleted;

			// Do grafu zaháčkuji své delegáty - pro reakci na události grafu :
            this._GraphEventsAdd(graph);

			// Umístit graf do položky (nahoru / dolů) a nastavit jeho Movable:
			if (graphPosition == GraphPositionType.FullPage || graphPosition == GraphPositionType.TopPart)
			{
				this._GraphSetPropertyForPart(graph, GraphMultiplexPart.PartA);
				multiplexItem.GraphA = graph;
			}
			else if (graphPosition == GraphPositionType.BottomPart)
			{
				this._GraphSetPropertyForPart(graph, GraphMultiplexPart.PartB);
                multiplexItem.GraphB = graph;
			}
		}
		/// <summary>
		/// Nastaví detaily grafu podle jeho umístění (A/B).
		/// </summary>
		/// <param name="graph"></param>
		/// <param name="part"></param>
		private void _GraphSetPropertyForPart(DColGrid graph, GraphMultiplexPart part)
		{
			switch (part)
			{
				case GraphMultiplexPart.PartA:
					graph.MultiplexPart = GraphMultiplexPart.PartA;
					graph.Header.ColumnHeaderMoveMode = DColumnsHeaderMoveMode.None;
					graph.Header.Column(3).HeaderMode = DColumnHeaderMode.TimeAxis;
					graph.Header.Column(3).RowFilterVisible = false;
					break;
				case GraphMultiplexPart.PartB:
					graph.MultiplexPart = GraphMultiplexPart.PartB;
					graph.Header.ColumnHeaderMoveMode = DColumnsHeaderMoveMode.ResizeParent;
					graph.Header.ColumnHeaderTopUpperLimit = 30;
					graph.Header.ColumnHeaderTopDownLimit = -30;
					// graph.Header.Column(3).HeaderMode = DColumnHeaderMode.StandardHeader;
					// graph.Header.Column(3).RowFilterVisible = true;
					break;
			}
		}
        /// <summary>
        /// Do daného grafu zaháčkuje svoje eventhandlery
        /// </summary>
        /// <param name="graph"></param>
        private void _GraphEventsAdd(DColGrid graph)
        {
			graph.ColumnHeaderParentTopMoving += new EventHandler(_GraphParentTopMoving);
			graph.ColumnHeaderParentTopMoved += new EventHandler(_HeaderParentTopMoved);
			graph.ColumnHeaderHeightChanging += new EventHandler(_HeaderHeightChanging);
			graph.ColumnHeaderHeightChanged += new EventHandler(_HeaderHeightChanged);
            graph.Enter += new EventHandler(this._GraphEnter);
            graph.RowChangedAfter += new GridGraphEventHandler(this._GraphRowChangedAfter);
            graph.GraphElementClick += new GridGraphEventHandler(this._GraphElementClick);
            graph.GraphRowDoubleClick += new GridGraphEventHandler(this._GraphRowDoubleClick);
            graph.EditorDataChanging += new EditorDataChangeHandler(this._EditorDataChanging);
            graph.EditorDataChanged += new EditorDataChangeHandler(this._EditorDataChanged);
            graph.FunctionDataChanged += new EditorDataChangeHandler(this._FunctionDataChanged);
            graph.RequestReloadAllRows += new GridGraphEventHandler(this._RequestReloadAllRows);
        }
		/// <summary>
        /// Z daného grafu odháčkuje svoje eventhandlery
        /// </summary>
        /// <param name="graph"></param>
        private void _GraphEventsRemove(DColGrid graph)
        {
			graph.ColumnHeaderParentTopMoving -= new EventHandler(_GraphParentTopMoving);
			graph.ColumnHeaderParentTopMoved -= new EventHandler(_HeaderParentTopMoved);
			graph.Enter -= new EventHandler(this._GraphEnter);
            graph.RowChangedAfter -= new GridGraphEventHandler(this._GraphRowChangedAfter);
            graph.GraphElementClick -= new GridGraphEventHandler(this._GraphElementClick);
            graph.GraphRowDoubleClick -= new GridGraphEventHandler(this._GraphRowDoubleClick);
            graph.EditorDataChanging -= new EditorDataChangeHandler(this._EditorDataChanging);
            graph.EditorDataChanged -= new EditorDataChangeHandler(this._EditorDataChanged);
            graph.FunctionDataChanged -= new EditorDataChangeHandler(this._FunctionDataChanged);
            graph.RequestReloadAllRows -= new GridGraphEventHandler(this._RequestReloadAllRows);
        }
        /// <summary>
		/// Metoda je zavolaná po dokončení přidávání grafů.
		/// </summary>
		internal void AddFinalize()
		{
            this.LayoutLoadFromRegistry();
		}
		#endregion
        #region AKTIVACE (přepínání aktivního grafu)
        /// <summary>
        /// Zajistí aktivaci stránky (položky multiplexu).
        /// Tato metoda se používá tehdy, když například datová vrstva si přeje přepnout záložku grafu.
        /// Tato metoda zavolá event this.ExecPageActivate a poté i výkonnou metodu this.ItemActivate().
        /// </summary>
        /// <param name="itemId"></param>
        protected void RunItemActivate(int itemId)
        {
            this.OnExecPageActivate(itemId);     // Event ExecPageActivate vede do vyšší vrstvy, a pravděpodobně vyvolá i zdejší metodu this.ItemActivate
            this.ItemActivate(itemId);           // Povinně aktivovat požadovanou stránku
        }
        /// <summary>
        /// Aktivuje položku multiplexoru.
        /// Položka je určená jejím indexem (počínaje 0).
        /// </summary>
        /// <param name="itemId"></param>
        internal void ItemActivate(int itemId)
        {
            if (!this._Parent.FormIsShowed) return;

            int currentId = this._CurrentItemId;
            bool activateEqual = (itemId == currentId);
            if (activateEqual)
            {	// Má se aktivovat shodná stránka => nebudu jí zhasínat a rozsvěcet, jen zajistím její Visible = true a skončím:
                this._CurrentItemSetVisible(true);
                return;
            }

            // Opustím dosavadní grafy (zhasnu a zapomenu), uchovám si jen jejich TimeRange:
            TimeRange timeRange = this._GetCurrentTimeRange();
            this._CurrentItemSetVisible(false);             // Zhasnu aktuální položky (to jsou ty nyní již minulé)
            this._GraphA = null;
            this._GraphB = null;

            // Najdu nově požadovanou položku multiplexu a zajistím její rozsvícení:
            GraphMultiplexItemCls multiplexItem;
            this._CurrentItemId = itemId;
            this.CurrentItemFound = this._GraphItemDict.TryGetValue(itemId, out multiplexItem);
            if (this.CurrentItemFound)
            {
                this.CurrentItem = multiplexItem;
                this._GraphA = multiplexItem.GraphA;
                this._GraphB = multiplexItem.GraphB;
            }

            // Nové položky (grafy) nadimenzuji, nastavím datum a rozsvítím grafy:
            this.LayoutAccept(LayoutAcceptItem.ColumnWidths | LayoutAcceptItem.HeaderHeight | LayoutAcceptItem.GraphBounds);    // Aktuální položky korektně umístím
            this._SetCurrentTimeRange(timeRange);
            this._CurrentItemSetVisible(true);              // Rozsvítím aktuální položky (nyní již ty nové)

            this.OnPageCurrentChange(itemId);               // Handler eventu, kdy se změnila aktivní strana

            this._CallGraphPageActivate(currentId, itemId); // Vyvolat akci PageActivate do přiměřených grafů na této stránce
        }
        /// <summary>
        /// Vrátí aktuální časový interval
        /// </summary>
        /// <returns></returns>
        private TimeRange _GetCurrentTimeRange()
        {
            if (this._GraphA != null) return this._GraphA.CurrentTimeRange;
            if (this._GraphB != null) return this._GraphB.CurrentTimeRange;
            return TimeRange.Empty;
        }

        private void _SetCurrentTimeRange(TimeRange timeRange)
        {
			this._SetCurrentTimeRange(this._GraphA, timeRange);
			this._SetCurrentTimeRange(this._GraphB, timeRange);
        }

		private void _SetCurrentTimeRange(DColGrid graph, TimeRange timeRange)
		{
			if (graph == null) return;
			if (timeRange.IsEmpty) return;
			// graph.Header.dete
			graph.Header.SetTimeAxisData(new TimeAxisSetAxisArgs(timeRange));
		}
        /// <summary>
        /// Nastaví Visible pro aktuální položky i pro splitter
        /// </summary>
        /// <param name="visible"></param>
        private void _CurrentItemSetVisible(bool visible)
        {
            if (this._GraphA != null) this._GraphA.Visible = visible;
            if (this._GraphB != null) this._GraphB.Visible = visible;
        }
        /// <summary>
        /// Metoda zajistí zavolání akce PageActivate do grafů, jejichž viditelnost se změnila.
        /// </summary>
        /// <param name="deactivateId">ID stránky, která se opouští (zhasíná)</param>
        /// <param name="activateId">ID stránky, která se aktivuje (rozsvěcuje)</param>
        private void _CallGraphPageActivate(int deactivateId, int activateId)
        {
            if (deactivateId == activateId) return;

            foreach (GraphMultiplexItemCls item in this._GraphItemDict.Values)
            {
                if (item.ItemID == deactivateId)
                {
                    if (item.GraphA != null) item.GraphA.GraphVisibleChanged(false);
                    if (item.GraphB != null) item.GraphB.GraphVisibleChanged(false);
                }
                else if (item.ItemID == activateId)
                {
                    if (item.GraphA != null) item.GraphA.GraphVisibleChanged(true);
                    if (item.GraphB != null) item.GraphB.GraphVisibleChanged(true);
                }
            }
        }
        #endregion
		#region HANDLERY RŮZNÝCH EVENTŮ Z DATOVÉHO ZDROJE A Z GRAFŮ SAMOTNÝCH
		/// <summary>
		/// Vstupní bod metody, která zpracuje výsledky asynchronního procesu čtení dat z datového zdroje.
		/// Protože datový zdroj může být sdílený mezi více grafy, ale výsledek asynchronního procesu se musí 
		/// adresovat tomu grafu, který daný požadavek vydal, proto obsahuje jak požadavek ID grafu (DataSourceRequest.SourceGraphID),
		/// tak obsahuje každý graf svoje ID. 
		/// Zde v multiplexoru najdu ID grafu, který podle ID má být adresátem události DataSourceRequestCompleted.
		/// A tam pošlu řízení.
		/// </summary>
		/// <param name="request">Požadavek, včetně Result dat</param>
		internal void DataSourceRequestCompleted(DataSourceRequest request)
		{
			foreach (GraphMultiplexItemCls item in this._GraphItemDict.Values)
				item.DataSourceRequestCompleted(request);
		}
		/// <summary>
		/// Handler události, kdy focus vstupuje do grafu.
		/// Multiplex si udržuje informaci o tom, který graf má nyní Focus - přinejmenším kvůli řešení řádkového filtru.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _GraphEnter(object sender, EventArgs e)
		{
			if (sender != null && sender is DColGrid)
			{
				DColGrid graphCurrent = sender as DColGrid;
				bool isChange = (graphCurrent != null && (this._GraphCurrent == null || (this._GraphCurrent != null && this._GraphCurrent.GraphID != graphCurrent.GraphID)));
				this._GraphCurrent = graphCurrent;

				// Změna => Event:
				if (isChange)
					this.OnGraphCurrentChange();
			}
		}
		/// <summary>
		/// Handler události, kdy uživatel na některém grafu doubleklikne na řádek grafu, a rád by viděl nějakou interakci s jinám grafem.
		/// Distribuce této události probíhá do všech grafů v multiplexoru, nejen do viditelných grafů.
        /// Distribuce se NEPROVEDE do grafu, jehož ID je předáno jako ID způsobitele (v e.SourceGraphID)
		/// (tam kde se kliklo, tam se to neříká - tam o tom vědí).
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _GraphRowDoubleClick(object sender, GridGraphEventArgs e)
		{
			List<DColGrid> graphList = this._GetTargetGraphList(e.SourceGraphID, e.TargetMode);
			foreach (DColGrid graph in graphList)
			{
				graph.GraphRowOtherDoubleClicked(sender, e);
				if (e.ResponseAcceptGraphId > 0) break;
			}
			this._GraphAfterEventShowItem(e);
		}
		/// <summary>
		/// Handler události, kdy uživatel na některém grafu aktivuje řádek jiný než byl aktivní dosud.
		/// Distribuce této události probíhá do všech grafů v multiplexoru, nejen do viditelných grafů.
        /// Distribuce se NEPROVEDE do grafu, jehož ID je předáno jako ID způsobitele (v e.SourceGraphID)
		/// (tam kde se kliklo, tam se to neříká - tam o tom vědí).
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _GraphRowChangedAfter(object sender, GridGraphEventArgs e)
		{
			List<DColGrid> graphList = this._GetTargetGraphList(e.SourceGraphID, e.TargetMode);
			foreach (DColGrid graph in graphList)
			{
				graph.RowOtherChanged(sender, e);
				if (e.ResponseAcceptGraphId > 0) break;
			}
			this._GraphAfterEventShowItem(e);
		}
		/// <summary>
		/// Handler události, kdy uživatel na některém grafu klikne na element grafu, ten zobrazí svoje vztahy,
		/// a uživatel by mohl chtít vidět vazby do jiných grafů.
		/// Distribuce této události probíhá do všech grafů v multiplexoru, nejen do viditelných grafů.
        /// Distribuce se NEPROVEDE do grafu, jehož ID je předáno jako ID způsobitele (v e.SourceGraphID)
		/// (tam kde se kliklo, tam se to neříká - tam o tom vědí).
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _GraphElementClick(object sender, GridGraphEventArgs e)
		{
			List<DColGrid> graphList = this._GetTargetGraphList(e.SourceGraphID, e.TargetMode);
			foreach (DColGrid graph in graphList)
			{
				graph.GraphElementOtherClicked(sender, e);
				if (e.ResponseAcceptGraphId > 0) break;
			}
			this._GraphAfterEventShowItem(e);
		}
		/// <summary>
		/// Handler události, kdy uživatel na některém grafu provádí editaci elementů (dosud neskončil).
		/// Distribuce této události probíhá do všech grafů v multiplexoru, nejen do viditelných grafů.
        /// Distribuce se NEPROVEDE do grafu, jehož ID je předáno jako ID způsobitele (v e.SourceGraphID).
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _EditorDataChanging(object sender, GridDataEventArgs e)
		{
			List<DColGrid> graphList = this._GetTargetGraphList(e.SourceGraphID, e.TargetMode);
			foreach (DColGrid graph in graphList)
			{
				graph.EditorDataOtherChanging(sender, e);
				// if (e.ResponseAcceptGraphId > 0) break;
			}
			this._GraphAfterEventShowItem(e);
		}
		/// <summary>
		/// Handler události, kdy uživatel na některém grafu dokončí editaci elementů.
		/// Distribuce této události probíhá do všech grafů v multiplexoru, nejen do viditelných grafů.
        /// Distribuce se NEPROVEDE do grafu, jehož ID je předáno jako ID způsobitele (v e.SourceGraphID).
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _EditorDataChanged(object sender, GridDataEventArgs e)
		{
			List<DColGrid> graphList = this._GetTargetGraphList(e.SourceGraphID, e.TargetMode);
			foreach (DColGrid graph in graphList)
			{
				graph.EditorDataOtherChanged(sender, e);
				// if (e.ResponseAcceptGraphId > 0) break;
			}
			this._GraphAfterEventShowItem(e);
		}
        /// <summary>
        /// Handler události, kdy uživatel na některém grafu dokončil funkci, která mění data.
        /// Distribuce této události probíhá do všech grafů v multiplexoru, nejen do viditelných grafů.
        /// Distribuce se NEPROVEDE do grafu, jehož ID je předáno jako ID způsobitele (v e.SourceGraphID).
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _FunctionDataChanged(object sender, GridDataEventArgs e)
        {
            List<DColGrid> graphList = this._GetTargetGraphList(e.SourceGraphID, e.TargetMode);
            foreach (DColGrid graph in graphList)
            {
                graph.FunctionDataOtherChanged(sender, e);
                // if (e.ResponseAcceptGraphId > 0) break;
            }
            this._GraphAfterEventShowItem(e);
        }
        /// <summary>
        /// Handler události, kdy uživatel na některém grafu provedl takovou věc, která si žádá přenačtení všech dat ve všech grafech.
        /// Distribuce této události probíhá do všech grafů v multiplexoru, nejen do viditelných grafů.
        /// Distribuce se NEPROVEDE do grafu, jehož ID je předáno jako ID způsobitele (v e.SourceGraphID).
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _RequestReloadAllRows(object sender, GridGraphEventArgs e)
        {
            List<DColGrid> graphList = this._GetTargetGraphList(e.SourceGraphID, e.TargetMode);
            foreach (DColGrid graph in graphList)
            {
                graph.ReloadRows();
            }
            this._GraphAfterEventShowItem(e);
        }
        /// <summary>
		/// Metoda po doběhnutí distribuce eventu mezi grafy.
		/// Zajistí zobrazení položky multiplexu, která obsahuje ten graf, který o to stojí.
		/// </summary>
		/// <param name="e"></param>
		private void _GraphAfterEventShowItem(GridEventArgs e)
		{
			if (!e.ResponseShowAcceptGraph || e.ResponseAcceptGraphId <= 0) return;              // Žádný graf nestojí o své zobrazení.

			int showItem = -1;
			foreach (GraphMultiplexItemCls item in this._GraphItemDict.Values)
			{
				if (item.GraphA != null && item.GraphA.GraphID == e.ResponseAcceptGraphId)
				{
					showItem = item.ItemID;
					break;
				}
				if (item.GraphB != null && item.GraphB.GraphID == e.ResponseAcceptGraphId)
				{
					showItem = item.ItemID;
					break;
				}
			}

			// Je třeba aktivovat některý item, a jiný než ten právě aktivní? 
			//  To by sice bylo možné provést napřímo (this.ItemActivate(showItem)),
			//  ale tím by nedošlo k vizuálnímu přepnutí TabPage (záložek), proto musíme vyvolat event "Multiplexor chce změnit záložku":
			if (showItem >= 0 && showItem != this._CurrentItemId)
				this.RunItemActivate(showItem);
		}
		#endregion
		#region METODY PRO ZÍSKÁNÍ DAT O GRAFECH
		/// <summary>
		/// Vrátí seznam referencí na grafy obsažené v multiplexu, které jsou not null.
		/// Metoda se používá jako zdroj lineárního soupisu pro obsluhu událostí, které se týkají všech grafů.
		/// </summary>
		/// <param name="graphId"></param>
		/// <returns></returns>
		private List<DColGrid> _GetAllGraphList()
		{
			return this._GetOtherGraphList(-1);
		}
		/// <summary>
		/// Vrátí seznam referencí na grafy obsažené v multiplexu, které jsou not null a mají SourceGraphID odlišné od zadaného.
		/// Metoda se používá jako zdroj lineárního soupisu pro obsluhu událostí, které se týkají všech grafů kromě zadaného.
		/// Pokud je třeba získat seznam úplně všech grafů, stačí zadat graphId = -1.
		/// </summary>
		/// <param name="graphId"></param>
		/// <returns></returns>
		private List<DColGrid> _GetOtherGraphList(int graphId)
		{
			List<DColGrid> result = new List<DColGrid>();
			foreach (GraphMultiplexItemCls item in this._GraphItemDict.Values)
			{
				if (item.GraphA != null && (graphId == -1 || item.GraphA.GraphID != graphId))
					result.Add(item.GraphA);
				if (item.GraphB != null && (graphId == -1 || item.GraphB.GraphID != graphId))
					result.Add(item.GraphB);
			}
			return result;
		}
		/// <summary>
		/// Vrátí seznam referencí na grafy obsažené v multiplexu, které jsou not null a mají SourceGraphID odlišné od zadaného,
		/// a vyhovují zadání aktivity dle parametru activityMode.
		/// Metoda se používá jako zdroj lineárního soupisu pro obsluhu událostí, které se týkají určených grafů kromě zadaného.
		/// Pokud je třeba získat seznam úplně všech grafů, stačí zadat graphId = -1.
		/// </summary>
		/// <param name="graphId">ID grafu, který akci vyvolal. Ten se vracet nebude.</param>
		/// <param name="activityMode">Režim aktivity, určuje výběr grafů, které se dostanou do výstupu.</param>
		/// <returns></returns>
		private List<DColGrid> _GetTargetGraphList(int graphId, TargetGraphCrossActivityMode targetMode)
		{
			List<DColGrid> result = new List<DColGrid>();
			if (targetMode == TargetGraphCrossActivityMode.OnThisPage || targetMode == TargetGraphCrossActivityMode.OnOtherPage || targetMode == TargetGraphCrossActivityMode.Anywhere)
			{
				foreach (GraphMultiplexItemCls item in this._GraphItemDict.Values)
				{
					bool aIsId = (item.GraphA != null && item.GraphA.GraphID == graphId);  // Graf A je ten graphId
					bool bIsId = (item.GraphB != null && item.GraphB.GraphID == graphId);  // Graf B je ten graphId
					bool pIsId = aIsId | bIsId;                                      // Na této stránce je graf graphId
					bool pgAdd =
						((targetMode == TargetGraphCrossActivityMode.OnThisPage && pIsId)
					  || (targetMode == TargetGraphCrossActivityMode.OnOtherPage && !pIsId)
					  || (targetMode == TargetGraphCrossActivityMode.Anywhere));   // Grafy z této stránky se mají podle režimu aktivity přidávat do výstupu

					if (pgAdd)
					{
						if (item.GraphA != null && (graphId == -1 || item.GraphA.GraphID != graphId))
							result.Add(item.GraphA);
						if (item.GraphB != null && (graphId == -1 || item.GraphB.GraphID != graphId))
							result.Add(item.GraphB);
					}
				}
			}
			return result;
		}
		/// <summary>
		/// Vrátí souhrn textů TabText z jednotlivých MultiplexItems
		/// </summary>
		/// <returns></returns>
		private string _GetTabCaption()
		{
			string result = "";
			string divider = "";
			foreach (GraphMultiplexItemCls item in this._GraphItemDict.Values)
			{
				result += divider + item.TabText;
				divider = "\r\n";
			}

			return result;
		}
		/// <summary>
		/// Najde a vrátí GID prvního z řádků
		/// </summary>
		/// <returns></returns>
		private GID _GetFirstGId()
		{
			foreach (GraphMultiplexItemCls item in this._GraphItemDict.Values)
			{
				if (item.ContainGraphA && item.GraphA.GraphDeclarationData.RowClassNumber != 0)
					return new GID(item.GraphA.GraphDeclarationData.RowClassNumber, 0);
				if (item.ContainGraphB && item.GraphB.GraphDeclarationData.RowClassNumber != 0)
					return new GID(item.GraphB.GraphDeclarationData.RowClassNumber, 0);
			}
			return GID.Empty;
		}
		#endregion
        #region LAYOUT: LOAD, SAVE, ACCEPT
        /// <summary>
        /// Načte layout z registru
        /// </summary>
        internal void LayoutLoadFromRegistry()
        {
            this.PanelLayout.Load("VisualLayout_" + this.FirstGId.ClassNumber.ToString(), "Layout");
            this.LayoutVerify();
        }
        /// <summary>
        /// Prověří, zda aktuální Layout (typicky načtený z registru) odpovídá aktuálním záložkám, případně Layout doplní.
        /// </summary>
        private void LayoutVerify()
        {
            // Widths:
            this.PanelLayout.VerifyWidths(25, 120, 240, 360);

            // Pages:
            foreach (GraphMultiplexItemCls multiplexItem in this._GraphItemDict.Values)
            {
                if (multiplexItem.GraphA != null)
                    this.PanelLayout.VerifyGraph(multiplexItem.ItemID, GraphMultiplexPart.PartA, 0, 0, 40, 18);
                if (multiplexItem.GraphB != null)
                    this.PanelLayout.VerifyGraph(multiplexItem.ItemID, GraphMultiplexPart.PartB, 200, 320, 40, 18);
            }
            List<int> pageIdx = new List<int>(this.PanelLayout.Pages.Keys);        // Seznam všech Pages v Layoutu
            foreach (int pageId in pageIdx)
            {
                if (!this._GraphItemDict.ContainsKey(pageId))
                    this.PanelLayout.Pages.Remove(pageId);
            }
        }
        /// <summary>
        /// Layout je připraven, lze jej ukládat.
        /// </summary>
        internal bool LayoutPrepared { get { return this.PanelLayout.LayoutPrepared; } }
        /// <summary>
        /// Uloží layout do registru
        /// </summary>
        internal void LayoutSaveToRegistry()
        {
            this._LayoutStoreForm();
            this.PanelLayout.Save();
        }
        /// <summary>
        /// Rozmístí prvky aktuální stránky do prostoru parenta podle definice layoutu aktuální stránky. Volitelně nastaví i vlastnosti Form.
        /// </summary>
        /// <param name="items">Položky k LayoutAccept</param>
        internal void LayoutAccept(LayoutAcceptItem items)
        {
            if ((items & LayoutAcceptItem.MainForm) != 0)
                this._LayoutAcceptForm();

            this.PanelLayout.CurrentPageIndex = this._CurrentItemId;           // Zajistím aktivaci stránky layoutu podle aktuálního multiplexu (to nestojí žádný čas)
            if (this._GraphA != null)
                this.PanelLayout.LayoutAcceptGraph(this._GraphA, items, null, this._GraphB);
            if (this._GraphB != null)
                this.PanelLayout.LayoutAcceptGraph(this._GraphB, items, this._GraphA, null);

        }
        /// <summary>
        /// Podle Layoutu nastaví vlastnosti Formu
        /// </summary>
        private void _LayoutAcceptForm()
        {
            Form form = this._Parent.FindForm();
            if (form != null)
                this.PanelLayout.LayoutAcceptForm(form);
        }
        /// <summary>
        /// Do Layoutu uloží vlastnosti Formu
        /// </summary>
        private void _LayoutStoreForm()
        {
            Form form = this._Parent.FindForm();
            if (form != null)
                this.PanelLayout.LayoutStoreForm(form);
        }
		#endregion
		#region LAYOUT: PŘÍJEM EVENTŮ Z GRAFŮ, KTERÉ MĚNÍ LAYOUT (vepisují se do layoutu a vyvolávají LayoutAccept)
		/// <summary>
		/// Graf přesouvá pozici Top a Height. To povede k LayoutAccept(), a zápisu hodnot do PanelLayout.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _GraphParentTopMoving(object sender, EventArgs e)
		{
			this._HeaderParentTopMove(sender, LayoutAcceptItem.GraphBounds | LayoutAcceptItem.Interactive);
		}
		/// <summary>
		/// Graf přesunul pozici Top a Height. To povede k LayoutAccept(), a zápisu hodnot do PanelLayout.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _HeaderParentTopMoved(object sender, EventArgs e)
		{
            this._HeaderParentTopMove(sender, LayoutAcceptItem.GraphBounds);
		}
		/// <summary>
		/// Probíhá změna výšky záhlaví. To nepovede k LayoutAccept(), pouze zapisuje hodnoty do PanelLayout.
		/// Vizuální změnu provádí třída DColGrid.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _HeaderHeightChanging(object sender, EventArgs e)
		{
			this._HeaderHeightChange(sender, true);
		}
		/// <summary>
		/// Proběhla změna výšky záhlaví. To nepovede k LayoutAccept(), pouze zapisuje hodnoty do PanelLayout.
		/// Vizuální změnu provádí třída DColGrid.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _HeaderHeightChanged(object sender, EventArgs e)
		{
			this._HeaderHeightChange(sender, false);
		}
		/// <summary>
		/// Reakce na přesun pozice Top a Height grafu.
		/// Nalezne Layout grafu, nalezne graf, a do Layoutu vepíše pozici Top grafu a zajistí LayoutAccept(interactive?).
		/// </summary>
        /// <param name="items">Položky k LayoutAccept</param>
		private void _HeaderParentTopMove(object sender, LayoutAcceptItem items)
		{
			DColGrid graph;
			GridPanelLayoutGraph layoutGraph = this._GetGraphLayout(sender, out graph);
			if (layoutGraph == null) return;
			if (graph == null) return;
			layoutGraph.HeaderTop = graph.Top;
			this.LayoutAccept(items);
		}
		/// <summary>
		/// Reakce na změnu hodnoty ColumnHeight grafu.
		/// Nalezne Layout grafu, nalezne graf, a do Layoutu vepíše hodnotu Height, neprovádí LayoutAccept(interactive?).
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="interactive"></param>
		private void _HeaderHeightChange(object sender, bool interactive)
		{
			DColGrid graph;
			GridPanelLayoutGraph layoutGraph = this._GetGraphLayout(sender, out graph);
			if (layoutGraph == null) return;
			if (graph == null) return;
			layoutGraph.HeaderHeight = (interactive ? graph.Header.ColumnHeaderHeightInteractive : graph.Header.ColumnHeaderHeight);
		}
		/// <summary>
		/// Pokusí se najít objekt GridPanelLayoutGraph (popisuje data layoutu daného grafu).
		/// Hledá jej na aktuální stránce layoutu, vybírá položku A/B podle sendera.
		/// </summary>
		/// <param name="sender"></param>
		/// <returns></returns>
		private GridPanelLayoutGraph _GetGraphLayout(object sender)
		{
			DColGrid grid;
			return _GetGraphLayout(sender, out grid);
		}
		/// <summary>
		/// Pokusí se najít objekt GridPanelLayoutGraph (popisuje data layoutu daného grafu).
		/// Hledá jej na aktuální stránce layoutu, vybírá položku A/B podle sendera.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="grid">Výstup objektu GridA / GridB</param>
		/// <returns></returns>
		private GridPanelLayoutGraph _GetGraphLayout(object sender, out DColGrid grid)
		{
			GraphMultiplexPart part = _GetMultiplexPart(sender);
			grid = null;
			switch (part)
			{
				case GraphMultiplexPart.PartA:
					grid = this.GraphA;
					return this.PanelLayout.CurrentGraphA;
				case GraphMultiplexPart.PartB:
					grid = this.GraphB;
					return this.PanelLayout.CurrentGraphB;
			}
			return null;
		}
		/// <summary>
		/// Vrátí část multiplexu (A/B), do níž patří graf, předaný jako object sender (odesilatel eventu).
		/// Údaj je uložen v Tagu grafu.
		/// </summary>
		/// <param name="sender"></param>
		/// <returns></returns>
		private GraphMultiplexPart _GetMultiplexPart(object sender)
		{
			// a) předaný objekt je DColGrid? Pak je Part uložen v property MultiplexPart:
			DColGrid grid = sender as DColGrid;
			if (grid != null)
				return grid.MultiplexPart;

			// b) předaný objekt je Control? Pak by mohl být Part uložen v Tagu:
			Control control = sender as Control;
			if (control != null && control.Tag is GraphMultiplexPart)
				return (GraphMultiplexPart)control.Tag;

			return GraphMultiplexPart.None;
		}
		#endregion
		#region VIRTUALIZACE GRAFU : Multiplexer tvoří most na metody / property grafu
		#region DISTRIBUCE ŘÍDÍCÍCH METOD
		/// <summary>
		/// Nastaví pozice sloupců Gridu podle definice sloupců v navázaných graph.Columns
		/// </summary>
        internal void AcceptColumnWidth(DColumnWidthEventArgs args, bool interactive)
		{
            this.PanelLayout.ImportColumnWidths(args.ColumnWidthList, interactive);
            this.LayoutAccept(LayoutAcceptItem.ColumnWidths | (interactive ? LayoutAcceptItem.Interactive : LayoutAcceptItem.None));
		}
		/// <summary>
		/// Zajistí prvotní načtení dat pro určitý datový okruh. Je možno volat kdykoliv. Veškerá předchozí data zahodí (dá Clear).
		/// Další načítání se už nevyvolává programem, ale uživatel si o ně řekne rozbalením prvku, který má subnodes.
		/// </summary>
		/// <param name="firstGId">Specifikace základních dat</param>
		internal void ReadFirstDataList(GID firstGId)
		{
			if (this._GraphA != null) this._GraphA.ReadFirstDataList(firstGId);
			else if (this._GraphB != null) this._GraphB.ReadFirstDataList(firstGId);
		}
		#endregion
		#region DISTRIBUCE DRAW, SUPPRESS DRAWING
		/// <summary>
		/// Tato metoda zajistí nové vykreslení objektu. Používá se namísto Invalidate() !!!
		/// Důvodem je to, že Invalidate() znovu vykreslí obsah bufferu - ale ten obsahuje "stará" data.
		/// Vyvolá událost PaintToBuffer() a pak přenese vykreslený obsah z bufferu do vizuálního controlu.
		/// </summary>
		internal void Draw()
		{
			if (this._GraphA != null) this._GraphA.Draw();
			if (this._GraphB != null) this._GraphB.Draw();
		}
		/// <summary>
		/// Umožní řídit potlačení vykreslování v hierarchii metod.
		/// Zajistí, že vykreslování bude potlačeno přinejmenším do párového vyvolání metody SuppressDrawingPop().
		/// Chování je obdobou chování Stacku: první Push zablokuje kreslení, následné Push a Pop to nezmění, poslední Pop to povolí.
		/// Podmínka: Push a Pop musí být v páru, jinak kreslení zamrzne.
		/// Řešení: je možno kdykoliv vložit SuppressDrawing = false a vykreslování ožije (nepárový zásobník se vynuluje).
		/// </summary>
		internal void SuppressDrawingPush()
		{
			if (this._GraphA != null) this._GraphA.SuppressDrawingPush();
			if (this._GraphB != null) this._GraphB.SuppressDrawingPush();
		}
		/// <summary>
		/// Umožní řídit potlačení vykreslování v hierarchii metod.
		/// Zajistí, že vykreslování bude potlačeno přinejmenším do párového vyvolání metody SuppressDrawingPop().
		/// Chování je obdobou chování Stacku: první Push zablokuje kreslení, následné Push & Pop to nezmění, poslední Pop to povolí.
		/// Podmínka: Push a Pop musí být v páru, jinak kreslení zamrzne.
		/// Řešení: je možno kdykoliv vložit SuppressDrawing = false a vykreslování ožije (nepárový zásobník se vynuluje).
		/// </summary>
		internal void SuppressDrawingPop()
		{
			if (this._GraphA != null) this._GraphA.SuppressDrawingPop();
			if (this._GraphB != null) this._GraphB.SuppressDrawingPop();
		}
		/// <summary>
		/// Potlačení kreslení při provádění rozsáhlejších změn. Po ukončení je třeba nastavit na true !
		/// Default = false = kreslení není potlačeno.
		/// Při provádění rozsáhlejších změn je vhodné nastavit na true, a po dokončení změn vrátit na false => tím se automaticky vyvolá Draw.
		/// </summary>
		internal bool SuppressDrawing
		{
			get
			{
				if (this._GraphA != null) return this._GraphA.SuppressDrawing;
				if (this._GraphB != null) return this._GraphB.SuppressDrawing;
				return false;
			}
			set
			{
				if (this._GraphA != null) this._GraphA.SuppressDrawing = value;
				if (this._GraphB != null) this._GraphB.SuppressDrawing = value;
			}
		}
		#endregion
		#region NAČTENÍ DATOVÝCH VERZÍ, SPOUŠTĚNÍ SADY PROCESŮ
		/// <summary>
		/// Metoda projde všechny datové zdroje a přečte z nich souhrn datových verzí, které mohou být aktivovány v toolbaru.
		/// Určí výchozí datovou verzi.
		/// </summary>
		/// <param name="itemList">Out seznam verzí</param>
		/// <param name="itemCurrent">Out aktuální verze (klíčem je její název)</param>
		/// <returns></returns>
		internal void RunActionGetDataVersions(out List<StringTag> itemList, out StringTag itemCurrent)
		{
			// Výstup této metody je součtem výstupů z jednotlivých datových zdrojů:
			itemList = new List<StringTag>();
			itemCurrent = null;

			// Požadavek do jednotlivých datových zdrojů:
			for (int dsi = 0; dsi < this._DataSourceList.Count; dsi++)
			{
				IDataSource dataSource = this._DataSourceList[dsi];
				DColGrid graph = this._FindFirstGraphWithSource(dataSource);
				if (graph != null)
				{
					// Požadavek do datového zdroje:
					DataSourceRequestGetDataVersions request = DataSourceRequestGetDataVersions.CreateDataVersionRequest(graph.GraphID, graph.GraphDeclarationData);
                    DataSourceCall.RunRequestSync(dataSource, request);      // dataSource.RunRequestSync(request);

					// Střádání výsledků:
					if (request.QueryResultDataVersionList != null)
						itemList.AddRange(request.QueryResultDataVersionList);
					if (itemCurrent == null && request.QueryResultDataVersionCurrent != null)
						itemCurrent = request.QueryResultDataVersionCurrent;
				}
			}
		}
		/// <summary>
		/// Metoda pro provedení datové akce, iniciované z toolbaru.
		/// Požadovanou akci provede pro každý datový zdroj, který je v multiplexoru zaregistrován.
		/// Pokud je požadováno více akcí, provedou se všechny.
		/// Aktivuje okno progresu a řídí jeho aktualizaci z rutin datových zdrojů.
		/// Datové zdroje mohou odmítnout provedení requestu.
		/// Pokud budou odmítnuty všechny požadavky ve všech zdrojích (tj. nebude se provádět ani jedna operace), vrací se false.
		/// Běžně metoda vrací true, synchronně = po doběhnutí všech požadavků.
		/// </summary>
		/// <param name="requestTypeList">Seznam požadovaných akcí</param>
		internal bool RunActionData(params DataSourceRequestType[] requestTypeList)
		{
			List<_RunActionPairTypeData> requestTypeDataList = _RunActionPairTypeData.CreatePairList(requestTypeList, (IComparable)null);
			return this._RunActionData(requestTypeDataList, null, false);
		}
		/// <summary>
		/// Metoda pro provedení datové akce, iniciované z toolbaru.
		/// Požadovanou akci provede pro každý datový zdroj, který je v multiplexoru zaregistrován.
		/// Pokud je požadováno více akcí, provedou se všechny.
		/// Aktivuje okno progresu a řídí jeho aktualizaci z rutin datových zdrojů.
		/// Datové zdroje mohou odmítnout provedení requestu.
		/// Pokud budou odmítnuty všechny požadavky ve všech zdrojích (tj. nebude se provádět ani jedna operace), vrací se false.
		/// Běžně metoda vrací true, synchronně = po doběhnutí všech požadavků. Vrací false, pokud všechny datové zdroje odmítnou požadavek vykonat.
		/// </summary>
		/// <param name="requestType">Typ požadavku</param>
		/// <param name="requestData">Data pro akci</param>
		internal bool RunActionData(DataSourceRequestType requestType, IComparable requestData)
		{
			List<_RunActionPairTypeData> requestTypeDataList = new List<_RunActionPairTypeData>();
			requestTypeDataList.Add(new _RunActionPairTypeData(requestType, requestData));
			return this._RunActionData(requestTypeDataList, null, false);
		}
        /// <summary>
        /// Metoda pro provedení datové akce, iniciované z toolbaru.
        /// Požadovanou akci provede pro každý datový zdroj, který je v multiplexoru zaregistrován.
        /// Pokud je požadováno více akcí, provedou se všechny.
        /// Aktivuje okno progresu a řídí jeho aktualizaci z rutin datových zdrojů.
        /// Datové zdroje mohou odmítnout provedení requestu.
        /// Pokud budou odmítnuty všechny požadavky ve všech zdrojích (tj. nebude se provádět ani jedna operace), vrací se false.
        /// Běžně metoda vrací true, synchronně = po doběhnutí všech požadavků. Vrací false, pokud všechny datové zdroje odmítnou požadavek vykonat.
        /// </summary>
        /// <param name="requestType">Typ požadavku</param>
        /// <param name="requestData">Data pro akci</param>
        /// <param name="runOnAllDataSources">Zavolat všechny registované datové zdroje, nejen ty použité v multiplexu</param>
        internal bool RunActionData(DataSourceRequestType requestType, IComparable requestData, bool runOnAllDataSources)
        {
            List<_RunActionPairTypeData> requestTypeDataList = new List<_RunActionPairTypeData>();
            requestTypeDataList.Add(new _RunActionPairTypeData(requestType, requestData));
            return this._RunActionData(requestTypeDataList, null, runOnAllDataSources);
        }
        /// <summary>
		/// Metoda pro provedení datové akce, iniciované z toolbaru.
		/// Požadovanou akci provede pro každý datový zdroj, který je v multiplexoru zaregistrován.
		/// Pokud je požadováno více akcí, provedou se všechny.
		/// Aktivuje okno progresu a řídí jeho aktualizaci z rutin datových zdrojů.
		/// Datové zdroje mohou odmítnout provedení requestu.
		/// Pokud budou odmítnuty všechny požadavky ve všech zdrojích (tj. nebude se provádět ani jedna operace), vrací se false.
		/// Běžně metoda vrací true, synchronně = po doběhnutí všech požadavků.
		/// </summary>
		/// <param name="requestTypeDataList">Seznam požadovaných akcí</param>
        /// <param name="runOnAllDataSources">Zavolat všechny registované datové zdroje, nejen ty použité v multiplexu</param>
        private bool _RunActionData(List<_RunActionPairTypeData> requestTypeDataList, Type runOnlyOnDataSourceType, bool runOnAllDataSources)
		{
			try
			{
                return this._RunActionDataRun(requestTypeDataList, runOnlyOnDataSourceType, runOnAllDataSources);
			}
			catch (Exception exc)
			{
				Dialog.ShowError(exc);
			}
			return false;
		}
		private bool _RunActionDataRun(List<_RunActionPairTypeData> requestTypeDataList, Type runOnlyOnDataSourceType, bool runOnAllDataSources)
		{
            if (!runOnAllDataSources)
                return _RunActionDataRunCurrentSources(requestTypeDataList, runOnlyOnDataSourceType);
            else
                return _RunActionDataRunAllSources(requestTypeDataList, runOnlyOnDataSourceType);
        }
        private bool _RunActionDataRunCurrentSources(List<_RunActionPairTypeData> requestTypeDataList, Type runOnlyOnDataSourceType)
        {
            this._ProcessForm = null;

            // Nejprve se pokusím získat mapu relativních časů pro jednotlivé requesty a datové zdroje:
            double timeTotal;
            bool refuseRequest;
            List<_RunActionRequestRunCls> runList = this._RunActionDataGetInfo(requestTypeDataList, runOnlyOnDataSourceType, out timeTotal, out refuseRequest);
            if (refuseRequest) return false;

            bool resultNeedReloadAllRows = false;     // Výsledek ReloadAll: jakýkoli request může přinést tuto odpověď !!!  Pak se provede na závěr, do všech grafů...

            // Připravím si okno, které bude zobrazovat postup průběhu požadavku:
            bool isTimeTotal = (timeTotal > 0D);
            this._ProcessMainLow = 0D;
            if (isTimeTotal)                  // Okno bude jen tehdy, když bude zapotřebí
                this._ProcessForm = ProcessForm.CreateInSeparateThread();

            // Každý požadavek pošlu do datového zdroje, který o něj stojí:
            bool allSourceCorrect = true;     // Součin výsledků ze všech zdrojů 
            int rqCnt = runList.Count;
            int dsCnt = this._DataSourceList.Count;
            for (int rqi = 0; rqi < rqCnt; rqi++)
            {	// Pro každý požadavek najdeme jeho metadata o procesu:
                _RunActionRequestRunCls requestRun = runList[rqi];
                for (int dsi = 0; dsi < dsCnt; dsi++)
                {	// Pro každý datový zdroj:
                    _RunActionRequestDsInfo requestInfo = (dsi >= 0 && dsi < requestRun.DsInfos.Length ? requestRun.DsInfos[dsi] : null);
                    if (requestInfo == null) continue;           // Pokud metadata nejsou vůbec, 
                    if (requestInfo.SkipThisRequest) continue;   //  anebo pokud obsahují Skip = true, přejdu na další zdroj...

                    // Tento požadavek budeme zpracovávat:
                    IDataSource dataSource = this._DataSourceList[dsi];
                    DColGrid graph = this._FindFirstGraphWithSource(dataSource);
                    if (graph != null)
                    {	// To je jen paranoia, protože jednak každý datový zdroj má svůj graf, 
                        //  a jednak i když by neměl, tak by nevznikla metadata requestInfo a byli bychom dávno na jiném requestu/zdroji.
                        DataSourceRequestType requestType = requestRun.RequestType;
                        IComparable requestData = requestRun.RequestData;

                        // Vizuální příprava procesu (relativní čas, krok progresu, název procesu, info do okna progresu):
                        this._RunActionDataVisualPrepareOne(requestRun, dataSource, requestInfo, timeTotal);

                        // Vlastní výkon akce (request):
                        DataSourceRequestDataFunction request = DataSourceRequestDataFunction.CreateDataSourceRequest(graph.GraphID, graph.GraphDeclarationData, requestType, requestData, this._RunActionInnerProgress);
                        DataSourceCall.RunRequestSync(dataSource, request, 1000 * requestInfo.TimeOut);      // dataSource.RunRequestSync(request);      timeout ze sekund 1000 * na milisekundy
                        if (request.ResultNeedReloadAllRows && !resultNeedReloadAllRows)
                            resultNeedReloadAllRows = true;

                        // Tento datový zdroj má nějaký problém => je to problém globální:
                        if ((!request.ResultCorrect || request.ResultException != null) && allSourceCorrect)
                            allSourceCorrect = false;

                        // Vizuální dokončení procesu (dokončení kroku progresu Main i Inner, posun _ProcessMainLow na _ProcessMainHigh
                        this._RunActionDataVisualDoneOne();
                    }
                }
            }

            // Je třeba reloadovat všechny řádky ve všech grafech?
            if (resultNeedReloadAllRows)
                this._RunReloadAllRows();

            // Pokud je připraveno okno progresu:
            if (this._ProcessForm != null)
                // Zavře okno:
                this._ProcessForm.ProcessClose();

            this._ProcessForm = null;

            return allSourceCorrect;
        }
        /// <summary>
        /// Dané požadavky spustí do všech datových zdrojů, které existují v systému (nemusí mít svůj graf !)
        /// </summary>
        /// <param name="requestTypeDataList"></param>
        /// <param name="runOnlyOnDataSourceType"></param>
        /// <returns></returns>
        private bool _RunActionDataRunAllSources(List<_RunActionPairTypeData> requestTypeDataList, Type runOnlyOnDataSourceType)
        {
            this._ProcessForm = null;

            // Každý požadavek pošlu do datového zdroje, který o něj stojí:
            List<IDataSource> sourceList = this._GetAllDataSources();
            bool allSourceCorrect = true;     // Součin výsledků ze všech zdrojů 
            int rqCnt = requestTypeDataList.Count;
            int dsCnt = sourceList.Count;
            for (int rqi = 0; rqi < rqCnt; rqi++)
            {
                _RunActionPairTypeData requestRun = requestTypeDataList[rqi];
                for (int dsi = 0; dsi < dsCnt; dsi++)
                {	// Pro každý datový zdroj:
                    IDataSource dataSource = sourceList[dsi];
                    DColGrid graph = this._FindFirstGraphWithSource(dataSource);
                    DataSourceRequestType requestType = requestRun.RequestType;
                    IComparable requestData = requestRun.RequestData;

                    DataSourceRequestDataFunction request = null;
                    if (graph != null)
                        request = DataSourceRequestDataFunction.CreateDataSourceRequest(graph.GraphID, graph.GraphDeclarationData, requestType, requestData, null);
                    else
                        request = DataSourceRequestDataFunction.CreateDataSourceRequest(0, null, requestType, requestData, null);

                    // Vlastní výkon akce (request):
                    DataSourceCall.RunRequestSync(dataSource, request);      // dataSource.RunRequestSync(request);

                    // Tento datový zdroj má nějaký problém => je to problém globální:
                    if ((!request.ResultCorrect || request.ResultException != null) && allSourceCorrect)
                        allSourceCorrect = false;
                }
            }
            return allSourceCorrect;
        }
        /// <summary>
        /// Vrátí soupis objektů všech datových zdrojů, přednostně vezme instance těch typů, které jsou použité v this, doplní je instancemi nepoužitých zdrojů z cache
        /// </summary>
        /// <returns></returns>
        private List<IDataSource> _GetAllDataSources()
        {
            IEnumerable<IDataSource> allDataSources = Steward.GetServiceObjectList<IDataSource>(true);  // Všechny datové zdroje, které o sobě tvrdí že jsou IDataSource
            List<IDataSource> regDataSources = this._DataSourceList;                                    // Zde registrované datové zdroje (použité v některém grafu)
            bool regExists = (regDataSources != null && regDataSources.Count > 0);

            // Vytvořím seznam, v němž budou všechny zdroje, a přednostně použiju objekty navázané v this multiplexu (regDataSources):
            List<IDataSource> result = new List<IDataSource>();
            foreach (IDataSource allDs in allDataSources)
            {
                if (allDs == null) continue;             // Tento datový zdroj nejde vytvořit, přeskočíme jej.
                IDataSource regDs = null;
                if (regExists)
                    regDs = regDataSources.Find(d => d.GetType() == allDs.GetType());                 // Tento zdroj máme v multiplexu použitý? Vezmeme jej, jinak vezmeme obecný cachovaný:
                
                if (regDs != null)
                    result.Add(regDs);
                else
                    result.Add(allDs);
            }
            return result;

        }
		/// <summary>
		/// Metoda se zeptá všech svých datových zdrojů, jak dlouho jim asi bude trvat provedení požadavků.
		/// Informace uloží do výstupního seznamu.
		/// Seznam obsahuje co řádek, to jeden request, a v poli DsInfos obsahuje metadata za každý datový zdroj (index metadat odpovídá indexu datového zdroje).
		/// Request se má spouštět pro každý datový zdroj, pro který jsou vyplněna metadata (ne tam, kde je null).
		/// Metoda prochází vstupní soupis požadavků a pro každý z nich volá každý datový zdroj, a zeptá se jej (requestem QueryAboutRequest),
		/// co on na tento požadavek (zda jej chce řešit, jak dlouho to bude trvat, jaké datový zdroj potřebuje requesty před tím a po tom).
		/// Více voz popis této metody, remarks. Tam je analogie.
		/// Vedlejší výstup (out parametr) obsahuje součet relativních časů.
		/// </summary>
		/// <remarks>
		/// Analogie: jsou čtyři kamarádi (datové zdroje), a někdo zvenku vznese požadavek "Půjdeme do hospody a ožereme se".
		/// To jsou dva requesty na každý datový zdroj.
		/// Zdejší metoda má za úkol sestavit kompletní plán akcí pro všechny účastníky (kamarády = datové zdroje).
		/// Projde tedy jeden požadavek za druhým (1=půjdeme do hospody, 2=ožereme se), 
		/// a ptá se každého kamaráda (datového zdroje), co on na to.
		/// Použije k tomu request QueryAboutRequest = "Co ty na takový nápad?"
		/// Každý kamarád se může vyjádřit v tomto smyslu:
		/// a) nepůjdu
		/// b) půjdu, a moje náklady na to budou XXX = určitý čas práce datového zdroje, relativně
		/// c) před tím ještě musím udělat toto a toto (vrátí seznam dalších requestů, které se musí zavolat před vlastním requestem)
		///    například: na požadavek "Půjdeme do hospody" vrátím prerequesty: "Musím se oholit" a "Musím do bankomatu" (i na pořadí záleží)
		/// d) obdobně se specifikuje, co se bude dělat po provedení daného requestu
		///    například: na požadavek "Ožereme se" vrátím postrequesty: "Musím objednat taxi domů" a "Cestou domů musím koupit kytku manželce jako omluvenku".
		/// Každý z kamarádů může mít jiné prerequesty i postrequesty (někdo není ženatý, někdo má peníze doma, ...)
		/// Zdejší metoda tedy korektně seřadí hlavní requesty a odpovědi datových zdrojů, a navíc projde rekurzivně i prerequesty i postrequesty:
		/// Zeptá se konkrétního kamaráda (jehož to jsou prerequesty a postrequesty) na jednotlivé požadavky, obdobně jako se ptala na hlavní požadavky.
		/// Například když je požadavek "Musím do bankomatu", tak datový zdroj vrátí postrequest "Musím odvézt domů kreditní kartu".
		/// Zdejší metoda tímto postupem hledá odpovědi na požadavky, a řadí je do lineární fronty úkolů.
		/// Ta pak může vypadat takto:
		///  akce        kamarád          Adam           Bertík        Cyril          Daniel
		///  1  Musím se oholit            ---          ano, T15        ---                          Prerequest Bertíka na požadavek Jdeme do hospody
		///  1  Musím do bankomatu         ---          ano, T90        ---                          Prerequest Bertíka na požadavek Jdeme do hospody
		///  2  Odvézt kreditní kartu                   ano, T20        ---                          Postrequest Bertíka na požadavek Musím do bankomatu
		///  « Jdeme do hospody »        ano, T30       ano, T60      ano, T45          ne           Daniel toto dělat nebude  (třeba už je dávno v hospodě)
		///  « Ožereme se »              ano, T120      ano, T25      ano, T30       ano, T60        Všichni to budou dělat
		///  1  Musím objednat taxi domů ano, T10         ---           ---             ---          Postrequest ode všech, ale pro každého bude v odděleném řádku
		///  1  Musím objednat taxi domů   ---          ano, T10        ---             ---          Postrequest ode všech, ale pro každého bude v odděleném řádku
		///  1  Zavolat švagrovi pro odvoz ---            ---         ano, T10                       Cyril nemá požadavek na taxi, ale na švagra
		///  1  Musím objednat taxi domů   ---            ---           ---          ano, T10        Postrequest ode všech, ale pro každého bude v odděleném řádku
		///  1  Koupit kytku manželce      ---          ano, T30        ---             ---          Další postrequest jen od Bertíka, jediný je ženatý
		///  1  Udobřit si milenku                                                   ano, T200       Daniel má problémy
		///  2  Koupit jí lístek do divadla                                          ano, T320       Daniel řeší problémy, postrequest...
		///    kde Txx je náročnost této akce, jejich součet je pak základnou pro ukazatel postupu.
		///    
		/// Z tohoto seznamu je zřetelné, že původní dva requesty se rozvinuly do docela obstojného plánu práce.
		/// Ne každou akci musí dělat každý kamarád. Každý kamarád má svoje individuální požadavky na PreRequest a PostRequest.
		/// Každý podřízený request může mít své vlastní podřízené PreRequesty a PostRequesty.
		/// Pořadí vnořených requestů v lineárním seznamu je co nejbližší nadřízenému requestu, při zachování pořadí z odpovědi.
		/// </remarks>
		/// <param name="requestTypeList">Soupis základních požadavků</param>
		/// <param name="timeTotal">Out součet časů</param>
		/// <param name="refuseRequest">Příznak, že některý datový zdroj odmítl daný požadavek</param>
		/// <returns>Soupis všech akcí (požadavků) a metadata o nich</returns>
		private List<_RunActionRequestRunCls> _RunActionDataGetInfo(List<_RunActionPairTypeData> requestTypeDataList, Type runOnlyOnDataSourceType, out double timeTotal, out bool refuseRequest)
		{
			refuseRequest = false;

			List<_RunActionRequestRunCls> runList = new List<_RunActionRequestRunCls>();

			bool runOnAllSources = (runOnlyOnDataSourceType == null);         // true = spustit na všech datových zdrojích
			int dsCnt = this._DataSourceList.Count;
			int rqCnt = requestTypeDataList.Count;
			int runCount = 0;

			if (dsCnt > 0 && rqCnt > 0)
			{
				for (int rqi = 0; rqi < rqCnt; rqi++)
				{	// Požadavky v daném pořadí:
					_RunActionPairTypeData requestTypeData = requestTypeDataList[rqi];

					// Výsledek pro tento jeden požadavek:
					_RunActionRequestRunCls requestRun = new _RunActionRequestRunCls(requestTypeData, true, dsCnt);
					int runIndex = runList.Count;            // Index tohoto externího požadavku
					// Vložit data požadavku do výsledného pole (rovnou), abych mohl vkládat před / za něj podřízené requesty:
					runList.Add(requestRun);

					for (int dsi = 0; dsi < dsCnt; dsi++)
					{	// Všechny datové zdroje nebo jen jeden?
						IDataSource dataSource = this._DataSourceList[dsi];
						if (runOnAllSources || dataSource.GetType() == runOnlyOnDataSourceType)
						{	// Požadavek se má spouštět na všech datových zdrojích, anebo na tomto konkrétním:
							DColGrid graph = this._FindFirstGraphWithSource(dataSource);
							if (graph != null)
							{
								// Požadavek na daný zdroj = Co ty budeš dělat, až tě požádám o danou akci?
								DataSourceRequestQuery request = DataSourceRequestQuery.CreateQueryAboutRequest(graph.GraphID, graph.GraphDeclarationData, requestTypeData.RequestType, requestTypeData.RequestData);
                                DataSourceCall.RunRequestSync(dataSource, request);      // dataSource.RunRequestSync(request);

								// Odmítnutí požadavku:
								if (request.QueryResultRefuseRequest)
									continue;
								runCount++;                 // Některý požadavek se bude vykonávat.

								// Z requestu převzít výsledky, týkající se daného requestu a daného datového zdroje:
								// Přebírám výsledky do objektu requestRun, který už je uložen v seznamu runList 
								//   (ale jehož index se možná posunul vlivem insertování prerequestů).
								requestRun.DsInfos[dsi] = _RunActionRequestDsInfo.CreateFromRequest(request);

								// Z requestu načíst a zpracovat result.QueryResultPreRequestList a QueryResultPostRequestList:
								//   To budu dělat i v případě, že datový zdroj nechce provádět hlavní akci 
								//    (když vrátil request.QueryResultSkipThisProcess == true)
								this._RunActionDataGetInfoLocal(dsi, graph, request.QueryResultPreRequestList, ref runIndex, ref runList, ref runCount);
								runIndex++;             // PostRequesty budu vkládat ZA moji pozici.
								this._RunActionDataGetInfoLocal(dsi, graph, request.QueryResultPostRequestList, ref runIndex, ref runList, ref runCount);
							}
						}
					}
				}
			}

			// Odmítnutí požadavku = když to nikdo nechce dělat:
			refuseRequest = (runCount == 0);

			// Dopočítat timeTotal ze všech requestů 
			//  (interně za jednotlivé datové zdroje se provádí sčítání v get property _RunActionRequestRunCls.RelativeTime):
			timeTotal = 0D;
			foreach (_RunActionRequestRunCls requestRun in runList)
				timeTotal += requestRun.RelativeTime;

			return runList;
		}
		/// <summary>
		/// Z odpovědi jistého datového zdroje přečte, zda má nějaké další požadavky na doplňující requesty Pre a Post,
		/// zmapuje je a přidá do ref seznamů. Rekurzivně.
		/// </summary>
		/// <param name="dataSourceIndex">Index datového zdroje. Jiné zdroje mě nyní nezajímají.</param>
		/// <param name="graph">Objekt prvního grafu tohoto datového zdroje, jeho ID a TAG se předává do requestu.</param>
		/// <param name="requestList">Seznam lokálních požadavků tohoto datového zdroje.</param>
		/// <param name="onIndex">Kam požadavky vkládat (na který index)</param>
		/// <param name="runList">Výsledný kompletní seznam všech požadavků včetně metadat o procesech, které se budou provádět</param>
		/// <param name="runCount">ref průběžně napočítávaný počet zařazených procesů</param>
		private void _RunActionDataGetInfoLocal(int dataSourceIndex, DColGrid graph, List<IComparable> requestList, ref int onIndex, ref List<_RunActionRequestRunCls> runList, ref int runCount)
		{
			if (requestList == null || requestList.Count == 0) return;

			DataSourceRequestType requestType = DataSourceRequestType.RunDataFunction;      // Typ požadavku pro lokální funkce
			foreach (IComparable requestData in requestList)
			{
				// Pokud pro tento datový zdroj (dataSourceIndex) už evidujeme tento požadavek (requestType),
				//   přeskočíme jej => došlo by k zacyklení!!!
				if (runList.Exists(delegate(_RunActionRequestRunCls r) { return r.ContainRequest(requestType, requestData, dataSourceIndex); }))
					continue;

				// Vytvořím položku do výstupního seznamu:
				_RunActionRequestRunCls requestRun = new _RunActionRequestRunCls(requestType, requestData, false, this._DataSourceList.Count);

				// Zjistím metadata o navázaném procesu:
				DataSourceRequestQuery request = DataSourceRequestQuery.CreateQueryAboutRequest(graph.GraphID, graph.GraphDeclarationData, requestType, requestData);
                DataSourceCall.RunRequestSync(this._DataSourceList[dataSourceIndex], request);      // this._DataSourceList[dataSourceIndex].RunRequestSync(request);

				// Odmítnutí požadavku:
				if (request.QueryResultRefuseRequest)
					continue;
				runCount++;

				// Z requestu převzít výsledky, týkající se daného requestu a daného datového zdroje:
				requestRun.DsInfos[dataSourceIndex] = _RunActionRequestDsInfo.CreateFromRequest(request);

				// Tuto položku zařadím tam, kam má být zařazena do výsledného seznamu:
				if (onIndex >= runList.Count)
					runList.Add(requestRun);
				else
					runList.Insert(onIndex, requestRun);
				
				// Rekurzivně řeším vnořené PreRequest a PostRequest:
				this._RunActionDataGetInfoLocal(dataSourceIndex, graph, request.QueryResultPreRequestList, ref onIndex, ref runList, ref runCount);
				onIndex++;             // PostRequesty budu vkládat ZA moji pozici.
				this._RunActionDataGetInfoLocal(dataSourceIndex, graph, request.QueryResultPostRequestList, ref onIndex, ref runList, ref runCount);
			}
		}
		/// <summary>
		/// Metoda připraví data pro vizualizaci jednoho procesu.
		/// </summary>
		/// <param name="requestRun"></param>
		/// <param name="dataSource"></param>
		/// <param name="requestInfo"></param>
		private void _RunActionDataVisualPrepareOne(_RunActionRequestRunCls requestRun, IDataSource dataSource, _RunActionRequestDsInfo requestInfo, double timeTotal)
		{
			// Relativní čas tohoto kroku v poměru k celku (=ratio)
			double timeProgress = (timeTotal > 0D ? requestInfo.RelativeTime / timeTotal : 0D);

			this._ProcessInnerActive = requestInfo.ActivateProgress;
			this._ProcessMainHigh = this._ProcessMainLow + timeProgress;

			// Pokud je připraveno okno progresu:
			if (this._ProcessForm != null)
			{
				// Název procesu: Text v progresu, horní hodnota Main progresu po dokončení tohoto kroku:
				string processName = requestInfo.ProcessName;
				if (processName == null)
				{
					string sourceName = dataSource.Properties.SourceName;           // Jméno zdroje
					if (String.IsNullOrEmpty(sourceName)) sourceName = dataSource.GetType().Name;
					processName = sourceName + ": " + requestRun.RequestType.ToString();
				}
				this._ProcessForm.ProcessMainText = processName;

				// Nastavení stylu Inner progresu:
				if (requestInfo.ActivateProgress && timeTotal > 0D)
				{	// Proces si sám řídí svůj progress:
					this._ProcessForm.ProcessInnerProgressStyle = ProgressBarStyle.Continuous;
					this._ProcessForm.ProcessInnerText = "Zahájení...";
					this._ProcessForm.ProcessInnerProgress = 0D;
				}
				else
				{	// Proces neřídí svůj progress, zobrazíme jeho progres jako Marque (ubíhající čas):
					this._ProcessForm.ProcessInnerProgressStyle = ProgressBarStyle.Marquee;
					this._ProcessForm.ProcessInnerProgress = 0D;
					this._ProcessForm.ProcessInnerText = "Pracuji...";
					this._ProcessForm.ProcessMainProgress = this._ProcessMainLow + (0.5D * timeProgress);
				}
			}
		}
		/// <summary>
		/// Metoda dokončí jeden krok progresu
		/// </summary>
		private void _RunActionDataVisualDoneOne()
		{
			// Pokud je připraveno okno progresu:
			if (this._ProcessForm != null)
			{	// Main progress po dokončení:
				this._ProcessForm.ProcessMainProgress = this._ProcessMainHigh;
				this._ProcessForm.ProcessInnerProgress = 1D;
			}
			this._ProcessMainLow = this._ProcessMainHigh;
		}
		/// <summary>Příznak true, zda je interní progres aktivní (to nastavuje datový zdroj jako metadata o procesu).</summary>
		private bool _ProcessInnerActive;
		/// <summary>Dolní hodnota progresu Main, na začátku jednotkového requestu</summary>
		private double _ProcessMainLow;
		/// <summary>Holní hodnota progresu Main, na konci jednotkového requestu</summary>
		private double _ProcessMainHigh;
		/// <summary>
		/// Úložiště pro formulář, který zobrazuje postup
		/// </summary>
		private ProcessForm _ProcessForm;
		/// <summary>
		/// Handler události, kdy datový zdroj v reakci na požadavek (request) postupně volá zobrazení progressu, za pomoci metody request.CallProgress().
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void _RunActionInnerProgress(object sender, CallbackArgs e)
		{
			if (this._ProcessForm == null) return;   // Pokud neexistuje Form, skončím. Může to být proto, že nebyl důvod jej vytvářet.

			if (e.Completed > 0F && this._ProcessInnerActive)
			{
				double processMainDimension = this._ProcessMainHigh - this._ProcessMainLow;

				double progressInner = (double)e.Completed;
				progressInner = (progressInner < 0D ? 0D : (progressInner > 1D ? 1D : progressInner));

				this._ProcessForm.ProcessInnerProgress = progressInner;

				double progressMain = this._ProcessMainLow + (progressInner * processMainDimension);
				this._ProcessForm.ProcessMainProgress = progressMain;
			}

			if (e.Texts != null && e.Texts.Length > 0)
				this._ProcessForm.ProcessInnerText = e.Texts[0];

			// this._ProcessForm.ProcessRefresh();
		}
		/// <summary>
		/// Metoda najde a vrátí první graf, jehož datovým zdrojem je daný objekt (exaktní instance).
		/// </summary>
		/// <param name="dataSource"></param>
		/// <returns></returns>
		private DColGrid _FindFirstGraphWithSource(IDataSource dataSource)
		{
			foreach (GraphMultiplexItemCls multiplexItem in this._GraphItemDict.Values)
			{
				if (multiplexItem.GraphA != null && Object.ReferenceEquals(multiplexItem.GraphA.GraphDeclarationData.DataSource, dataSource))
					return multiplexItem.GraphA;
				if (multiplexItem.GraphB != null && Object.ReferenceEquals(multiplexItem.GraphB.GraphDeclarationData.DataSource, dataSource))
					return multiplexItem.GraphB;
			}
			return null;
		}
		/// <summary>
		/// Třída pro uchování dvojice informací: Typ požadavku, Data požadavku
		/// </summary>
		private class _RunActionPairTypeData
		{
			internal _RunActionPairTypeData(DataSourceRequestType requestType, IComparable requestData)
			{
				this.RequestType = requestType;
				this.RequestData = requestData;
			}
			/// <summary>
			/// Vizualizace
			/// </summary>
			/// <returns></returns>
			public override string ToString()
			{
				return "{Type=" + this.RequestType.ToString() + "; Data=" + this.RequestData.ToString() + "}";
			}
			/// <summary>
			/// Typ požadavku
			/// </summary>
			internal DataSourceRequestType RequestType;
			/// <summary>
			/// Data pro požadavek
			/// </summary>
			internal IComparable RequestData;

			/// <summary>
			/// Služba: z dodaného seznamu typů požadavků vytvoří seznam párů (typ + data).
			/// </summary>
			/// <param name="requestTypeList">Seznam typů požadavku</param>
			/// <param name="requestData">Hodnota Data do každého páru</param>
			/// <returns></returns>
			internal static List<_RunActionPairTypeData> CreatePairList(DataSourceRequestType[] requestTypeList, IComparable requestData)
			{
				List<_RunActionPairTypeData> result = new List<_RunActionPairTypeData>();
				if (requestTypeList != null)
				{
					foreach (DataSourceRequestType requestType in requestTypeList)
						result.Add(new _RunActionPairTypeData(requestType, requestData));
				}
				return result;
			}
		}
		/// <summary>
		/// Třída pro uchování informací o jednom requestu a sady odpovědí z datových zdrojů.
		/// </summary>
		private class _RunActionRequestRunCls
		{
			internal _RunActionRequestRunCls(DataSourceRequestType requestType, IComparable requestData, bool externalRequest, int dsCount)
			{
				this.RequestType = requestType;
				this.RequestData = requestData;
				this.ExternalRequest = externalRequest;
				this.DsInfos = new _RunActionRequestDsInfo[dsCount];
			}
			internal _RunActionRequestRunCls(_RunActionPairTypeData requestTypeData, bool externalRequest, int dsCount)
			{
				this.RequestType = requestTypeData.RequestType;
				this.RequestData = requestTypeData.RequestData;
				this.ExternalRequest = externalRequest;
				this.DsInfos = new _RunActionRequestDsInfo[dsCount];
			}
			/// <summary>
			/// Vizualizace
			/// </summary>
			/// <returns></returns>
			public override string ToString()
			{
				return "{Type=" + this.RequestType.ToString() + 
					(this.ExternalRequest ? " (External)" : " (Local)") + 
					"; Data=" + this.RequestData.ToString() + 
					"; ActiveDsCount=" + this.ActiveDsInfoCount.ToString() + " }";
			}
			/// <summary>
			/// Typ požadavku
			/// </summary>
			internal DataSourceRequestType RequestType;
			/// <summary>
			/// Data pro požadavek
			/// </summary>
			internal IComparable RequestData;
			/// <summary>
			/// Původ požadavku: true = externí (z nadřízeného kódu) anebo
			/// false = lokální, tento požadavek vznikl jako navázaný požadavek z určitého datového zdroje 
			/// (například když externí požadavek ActivateData v určitém datovém zdroji vygeneruje požadavek SaveData před vlastní Activate)
			/// </summary>
			internal bool ExternalRequest;
			/// <summary>
			/// Pole pro uchování metadat o requestu z jednotlivých datových zdrojů, pořadí odpovídá pořadí datových zdrojů.
			/// Pokud je v některém prvku null, jde o lokální request, a pro dotyčný datový zdroj (kde je uloženo null) se nemá spouštět.
			/// </summary>
			internal _RunActionRequestDsInfo[] DsInfos;
			/// <summary>
			/// Relativní čas tohoto requestu. Jde o součet ze všech položek v DsInfos (které nejsou null).
			/// </summary>
			internal double RelativeTime
			{
				get
				{
					double relativeTime = 0D;
					foreach (_RunActionRequestDsInfo dsInfo in this.DsInfos)
					{
						if (dsInfo != null)
							relativeTime += dsInfo.RelativeTime;
					}
					return relativeTime;
				}
			}
            /// <summary>
            /// TimeOut této operace
            /// </summary>
            internal int TimeOut;
			/// <summary>
			/// Počet aktivních metadat (položek za datové zdroje, které se budou vykonávat)
			/// </summary>
			internal int ActiveDsInfoCount
			{
				get
				{
					int count = 0;
					foreach (_RunActionRequestDsInfo dsInfo in this.DsInfos)
					{
						if (dsInfo != null && !dsInfo.SkipThisRequest)
							count++;
					}
					return count;
				}
			}
			/// <summary>
			/// Metoda zjistí, zda v tomto objektu je dotazovaný request pro daný datový zdroj.
			/// Vrátí true, když máme metadata o procesu (i když by obsahovala Skip = true).
			/// </summary>
			/// <param name="requestType"></param>
			/// <param name="dataSourceIndex"></param>
			/// <returns></returns>
			internal bool ContainRequest(DataSourceRequestType requestType, IComparable requestData, int dataSourceIndex)
			{
				if (this.RequestType != requestType) return false;   // Tento záznam je pro jiný request, vůbec to nemusím řešit
				if (this.RequestData.CompareTo(requestData) != 0) return false;       // Shodný request, ale jiná data => je to něco jiného.
				if (dataSourceIndex < 0 || dataSourceIndex >= this.DsInfos.Length) return false;   // Nemožné zadání, to nemáme.
				return (this.DsInfos[dataSourceIndex] != null);      // true, pokud pro daný datový zdroj máme metadata = proces se bude provádět.
			}
		}
		/// <summary>
		/// Třída pro uchování metadat o requestu, získaných z odpovědi datového zdroje na dotaz QueryAboutRequest
		/// </summary>
		private class _RunActionRequestDsInfo
		{
			internal _RunActionRequestDsInfo(DataSourceRequestQuery request)
			{
				this.SkipThisRequest = request.QueryResultSkipThisProcess;
				this.ActivateProgress = request.QueryResultActivateProgress;
				this.RelativeTime = request.QueryResultRelativeTime;
                this.TimeOut = request.QueryResultRunTimeout;
				this.ProcessName = request.QueryResultProcessName;
			}
			internal _RunActionRequestDsInfo(bool skipThisRequest, bool activateProgress, double relativeTime, string actionName)
			{
				this.SkipThisRequest = skipThisRequest;
				this.ActivateProgress = activateProgress;
				this.RelativeTime = relativeTime;
				this.ProcessName = actionName;
			}
			/// <summary>
			/// Vizualizace
			/// </summary>
			/// <returns></returns>
			public override string ToString()
			{
				return "{ProcessName=" + this.ProcessName + (this.SkipThisRequest ? " (Skip)" : "") + "; RelativeTime = " + this.RelativeTime.ToString() + " }";
			}
			internal bool SkipThisRequest;
			/// <summary>
			/// Aktivovat konkrétní progress pro tento úkol?
			/// false = progres bude marquee (volnoběžný proužek) 
			/// true = datový zdroj bude řídit progres vlastními silami
			/// </summary>
			internal bool ActivateProgress;
			/// <summary>
			/// Relativní čas jak dlouho relativně bude práce na tomto požadavku trvat.
			/// Výchozí hodnota = 0. Značí, že pro tento proces nebude rezervován žádný úsek ukazatele.
			/// Standardní hodnota = 1. Značí, že pro proces bude rezervována rovnocenná poměrná část ukazatele, jako pro ostatní procesy.
			/// Procesy, které jsou rychlejší, mají nastavit přiměřeně menší hodnotu.
			/// Procesy, které jsou pomalejší, mají nastavit vyšší hodnotu, tím si alokují větší poměrnou část ukazatele.
			/// </summary>
			internal double RelativeTime;
            /// <summary>
            /// Timeout této akce v sekundách
            /// </summary>
            internal int TimeOut;
			/// <summary>
			/// Název procesu = jaký text má být zobrazen pro tento proces.
			/// Výchozí hodnota = null, použije se defaultní název (název datového zdroje: název požadavku).
			/// </summary>
			internal string ProcessName;
			/// <summary>
			/// Vrátí metadata o procesu na základě odpovědí v requestu.
			/// Vrací nový objekt i pro requesty, které se nemají provádět (které mají request.QueryResultSkipThisProcess == true).
			/// </summary>
			/// <param name="request"></param>
			/// <returns></returns>
			internal static _RunActionRequestDsInfo CreateFromRequest(DataSourceRequestQuery request)
			{
				// if (request.QueryResultSkipThisProcess)
				// 	return null;
				return new _RunActionRequestDsInfo(request);
			}
		}
		/// <summary>
		/// Metoda zajistí reload všech řádků ve všech grafech
		/// </summary>
		private void _RunReloadAllRows()
		{
			foreach (GraphMultiplexItemCls multiplexItem in this._GraphItemDict.Values)
			{
				if (multiplexItem.GraphA != null)
					this._RunReloadAllRowsOne(multiplexItem.GraphA);
				if (multiplexItem.GraphB != null)
					this._RunReloadAllRowsOne(multiplexItem.GraphB);
			}
		}
		/// <summary>
		/// Metoda zajistí reload všech řádků v daném grafu
		/// </summary>
		/// <param name="gridGraph"></param>
		private void _RunReloadAllRowsOne(DColGrid gridGraph)
		{
			if (gridGraph == null) return;
			gridGraph.ReloadRows();
		}
		#endregion
		#region DISTRIBUOVANÉ VLASTNOSTI PRO EDITOR (GLOBAL_FUNCTION, UNDO, REDO, ZOOM, SNAP TO GRID)
		/// <summary>
		/// Metoda pro provedení globální funkce, iniciované z toolbaru
		/// </summary>
		/// <param name="e"></param>
		internal void RunGlobalFunction(FunctionGlobalRunArgs runArgs)
		{
			// Request na datovou funkci (RunDataFunction) s danými daty funkce (requestData):
			DataSourceRequestType requestType = DataSourceRequestType.RunDataFunction;
			IComparable requestData = runArgs.ResultCallRequestData;
			List<_RunActionPairTypeData> requestTypeDataList = new List<_RunActionPairTypeData>();
			requestTypeDataList.Add(new _RunActionPairTypeData(requestType, requestData));

			// Cílový datový zdroj (konkrétní anebo všichni):
			Type targetSourceType = runArgs.ResultCallDataSourceOfType;

            // Spustit výkonnou část, která vykonává sadu globálních funkcí dle seznamu requestTypeDataList:
			this._RunActionData(requestTypeDataList, targetSourceType, false);
		}
		/// <summary>
		/// { get; } Obsahuje informaci o tom, zda je možno provést Undo akci.
		/// Pouze pro graf A (horní).
		/// </summary>
		internal bool IsUndoEnabled
		{
			get
			{
				if (this._GraphA != null) return this._GraphA.IsUndoEnabled;
				return false;
			}
		}
		/// <summary>
		/// { get; } Obsahuje informaci o tom, zda je možno provést Redo akci.
		/// Pouze pro graf A (horní).
		/// </summary>
		internal bool IsRedoEnabled
		{
			get
			{
				if (this._GraphA != null) return this._GraphA.IsRedoEnabled;
				return false;
			}
		}
		/// <summary>
		/// { get; } Obsahuje informaci o tom, zda se právě provádí Zooming.
		/// Pouze pro graf A (horní).
		/// Na to by měla reagovat ikona Zoomu, typicky CheckBox: Checked = IsInZoomingMode;
		/// Změna hodnoty vyvolává event EditorStateChanged.
		/// </summary>
		internal bool IsInZoomingMode
		{
			get
			{
				if (this._GraphA != null) return this._GraphA.IsInZoomingMode;
				return false;
			}
		}
		/// <summary>
		/// { get; set; } Obsahuje / nastavuje informaci o tom, zda graf je právě ve stavu SnapToGrid.
		/// </summary>
		internal bool SnapToGrid
		{
			get
			{
				if (this._GraphA != null) return this._GraphA.SnapToGrid;
				return false;
			}
			set
			{
				if (this._GraphA != null) this._GraphA.SnapToGrid = value;
				if (this._GraphB != null) this._GraphB.SnapToGrid = value;
			}
		}
		/// <summary>
		/// Metoda, která provede jeden krok Undo.
		/// Pouze pro graf A (horní).
		/// Pokud není k dispozici žádný krok Undo, neprovede nic.
		/// Existenci Undo kroků lze detekovat příznakem bool IsUndoEnabled.
		/// </summary>
		internal void MakeUndo()
		{
			if (this._GraphA != null) this._GraphA.MakeUndo();
		}
		/// <summary>
		/// Metoda, která provede jeden krok Redo.
		/// Pouze pro graf A (horní).
		/// Pokud není k dispozici žádný krok Redo, neprovede nic.
		/// Existenci Undo kroků lze detekovat příznakem bool IsRedoEnabled.
		/// </summary>
		internal void MakeRedo()
		{
			if (this._GraphA != null) this._GraphA.MakeRedo();
		}
		/// <summary>
		/// Metoda, která aktivuje režim Zoom v prostoru grafu.
		/// Pro oba grafy (A a B)
		/// </summary>
		internal void ZoomActivate()
		{
			if (this._GraphA != null) this._GraphA.ZoomActivate();
			if (this._GraphB != null) this._GraphB.ZoomActivate();
		}
		/// <summary>
		/// Metoda, která deaktivuje režim Zoom v prostoru grafu.
		/// Pro oba grafy (A a B)
		/// </summary>
		internal void ZoomDeactivate()
		{
			if (this._GraphA != null) this._GraphA.ZoomDeactivate();
			if (this._GraphB != null) this._GraphB.ZoomDeactivate();
		}
		#endregion
		#endregion
    }
	#endregion
	#region CLASS GraphMultiplexItemCls : Data jedné stránky multiplexoru
	/// <summary>
	/// Data jedné stránky multiplexoru
	/// </summary>
	internal class GraphMultiplexItemCls
	{
		internal GraphMultiplexItemCls(int itemId)
		{
			this._ItemID = itemId;
		}
		private int _ItemID;
		/// <summary>
		/// Vstupní bod metody, která zpracuje výsledky asynchronního procesu čtení dat z datového zdroje.
		/// Protože datový zdroj může být sdílený mezi více grafy, ale výsledek asynchronního procesu se musí 
		/// adresovat tomu grafu, který daný požadavek vydal, proto obsahuje jak požadavek ID grafu (DataSourceRequest.SourceGraphID),
		/// tak obsahuje každý graf svoje ID. 
		/// Zde v multiplexoru najdu ID grafu, který podle ID má být adresátem události DataSourceRequestCompleted.
		/// A tam pošlu řízení.
		/// </summary>
		/// <param name="request">Požadavek, včetně Result dat</param>
		internal void DataSourceRequestCompleted(DataSourceRequest request)
		{
			if (this._GraphA != null && this._GraphA.GraphID == request.GraphID) this._GraphA.DataSourceRequestCompleted(request);
			if (this._GraphB != null && this._GraphB.GraphID == request.GraphID) this._GraphB.DataSourceRequestCompleted(request);
		}
		/// <summary>Reference na horní graf (který je připevněn ke splitteru H, pod hlavičkou)</summary>
		private DColGrid _GraphA;
		/// <summary>Reference na dolní graf (nemusí být použit, pak je null)</summary>
		private DColGrid _GraphB;
		/// <summary>Vzdálenost splitteru platná pro tuto stránku. -1 pokud není použit.</summary>
		internal int SplitterDistance;
		/// <summary>Text na tabu: je přebírán z textu pro graf A</summary>
		private string _TabText;
		/// <summary>Text na splitteru: je přebírán z textu pro graf B</summary>
		private string _SplitterText;
		/// <summary>ID položky</summary>
		internal int ItemID { get { return this._ItemID; } }
		/// <summary>Reference na horní graf (který je připevněn ke splitteru H, pod hlavičkou)</summary>
        internal DColGrid GraphA
		{
			get { return this._GraphA; }
			set
			{
				this._GraphA = value;
				if (this._GraphA != null)
					this._TabText = this._GraphA.GraphDeclarationData.GraphCaption;
			}
		}
		/// <summary>Reference na dolní graf (nemusí být použit, pak je null)</summary>
        internal DColGrid GraphB
		{
			get { return this._GraphB; }
			set
			{
				this._GraphB = value;
				if (this._GraphB != null)
					this._SplitterText = this._GraphB.GraphDeclarationData.GraphCaption;
			}
		}
		/// <summary>text na záložku pro tuto položku Multiplexu (převzat z grafu A)</summary>
		internal string TabText { get { return this._GetTextCaption(this._GraphA); } }
		/// <summary>text na splitter pro tuto položku Multiplexu (převzat z grafu B)</summary>
		internal string SplitterText { get { return this._GetTextCaption(this._GraphB); } }
		/// <summary>Existuje horní graf ?</summary>
		internal bool ContainGraphA { get { return (this._GraphA != null); } }
		/// <summary>Existuje dolní graf ?</summary>
		internal bool ContainGraphB { get { return (this._GraphB != null); } }
		/// <summary>true, pokud je volný celý prostor multiplexu (neexistuje ani graf A ani B)</summary>
		internal bool IsEmpty { get { return (!ContainGraphA && !ContainGraphB); } }
		/// <summary>true, pokud je volný horní prostor multiplexu (neexistuje graf A)</summary>
		internal bool IsEmptyA { get { return (!ContainGraphA); } }
		/// <summary>true, pokud je volný dolní prostor multiplexu (neexistuje ani graf B, buď neexistuje graf A anebo existuje, ale je jen na horní polovině)</summary>
		internal bool IsEmptyB { get { return (!ContainGraphB && (!ContainGraphA || (ContainGraphA && this._GraphA.GraphDeclarationData.GraphPosition == GraphPositionType.TopPart))); } }
		/// <summary>
		/// Vrátí text do titulku grafu
		/// </summary>
		/// <param name="gridGraph"></param>
		/// <returns></returns>
		private string _GetTextCaption(DColGrid gridGraph)
		{
			if (gridGraph == null) return String.Empty;
			if (gridGraph.GraphDeclarationData == null) return "Graf " + gridGraph.DefaultGraphType.ToString();
			if (String.IsNullOrEmpty(gridGraph.GraphDeclarationData.GraphCaption)) return "Graf " + gridGraph.DefaultGraphType.ToString();
			return gridGraph.GraphDeclarationData.GraphCaption;
		}
	}
	#endregion
    #region CLASS GridPanelLayout : obsahuje paměť pro persistenci rozložení prvků GridPanelu
    /// <summary>
    /// GridPanelLayout : obsahuje paměť pro persistenci rozložení prvků GridPanelu
    /// </summary>
    internal class GridPanelLayout
    {
        #region KONSTRUKCE
        internal GridPanelLayout()
        {
            ColumnWidthList = new List<int>();
            Pages = new Dictionary<int, GridPanelLayoutPage>();
        }
        #endregion
        #region PROPERTY
        /// <summary>
        /// Složka v registrech
        /// </summary>
        internal string RegFolder { get; private set; }
        /// <summary>
        /// Jméno v registrech
        /// </summary>
        internal string RegName { get; private set; }
        /// <summary>
        /// Seznam šířek jednotlivých sloupců (všech = tj. včetně systémového)
        /// </summary>
        internal List<int> ColumnWidthList { get; private set; }
        /// <summary>
        /// Index Aktuálně platné stránky layoutu.
        /// </summary>
        internal int CurrentPageIndex { get; set; }
        /// <summary>
        /// Vrátí aktuální stránku layoutu, podle indexu CurrentPageIndex. Anebo null.
        /// </summary>
        /// <returns></returns>
        private GridPanelLayoutPage _GetCurrentPage()
        {
            if (this.CurrentPageExists)
                return this.Pages[this.CurrentPageIndex];
            return null;
        }
        /// <summary>
        /// Počet stránek s grafy. Anebo -1 když není inicializováno.
        /// </summary>
        internal int PagesCount { get { return (this.Pages == null ? -1 : this.Pages.Count); } }
        /// <summary>
        /// Seznam stránek s grafy
        /// </summary>
        internal Dictionary<int, GridPanelLayoutPage> Pages { get; private set; }
        /// <summary>
        /// Příznak, že MainForm je maximalizovaný: "M"= max, "S"=standard, "" = nenačteno
        /// </summary>
        internal string MainFormWindowState { get; set; }
        /// <summary>
        /// Pozice MainForm (dovolí tak uložit pozici ve vícemonitorovém provozu)
        /// </summary>
        internal Rectangle MainFormBounds { get; set; }
        #endregion
        #region PERSISTENCE
        /// <summary>
        /// Vynuluje se
        /// </summary>
        internal void Clear()
        {
            this.RegFolder = null;
            this.RegName = null;
            this.ColumnWidthList = new List<int>();
            this.Pages = new Dictionary<int, GridPanelLayoutPage>();
            this.MainFormWindowState = "";
            this.MainFormBounds = Rectangle.Empty;
        }
        /// <summary>
        /// Načte layout z registru
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="name"></param>
        internal void Load(string folder, string name)
        {
            if (String.IsNullOrEmpty(folder) || String.IsNullOrEmpty(name))
                Throw.SysError(MessageInfo.Get("Panellayout.Load(): missing value for Folder and/or Name."));

            this.RegFolder = folder;
            this.RegName = name;

            this.MainFormWindowState = Steward.WinRegReadString(folder, name + "_WS", "");
            this.MainFormBounds = ComponentLibrary.RectangleDeserialize(Steward.WinRegReadString(folder, name + "_WB", null));

            int dc = Steward.WinRegReadInt32(folder, name + "_DC", 3);                                 // Počet uložených sloupců
            if (dc < 3) dc = 3;

            this.ColumnWidthList = new List<int>();
            for (int c = 0; c < dc; c++)
                this.ColumnWidthList.Add(Steward.WinRegReadInt32(folder, name + "_D" + c.ToString(), 0));
            if (this.ColumnWidthList[0] < 10) this.ColumnWidthList[0] = 10;
            if (this.ColumnWidthList[1] < 10) this.ColumnWidthList[1] = 10;
            if (this.ColumnWidthList[2] < 4) this.ColumnWidthList[2] = 4;

            int pc = Steward.WinRegReadInt32(folder, name + "_PC", 3);                                 // Počet uložených stránek

            this.Pages = new Dictionary<int, GridPanelLayoutPage>();
            for (int p = 0; p < pc; p++)
            {
                GridPanelLayoutPage page = new GridPanelLayoutPage(p);
                page.Load(folder, name, p);
                if (!page.IsEmpty)
                    this.Pages.Add(p, page);
            }
        }
        /// <summary>
        /// Layout je připraven, lze jej ukládat.
        /// </summary>
        internal bool LayoutPrepared { get { return (!String.IsNullOrEmpty(this.RegFolder) && !String.IsNullOrEmpty(this.RegName)); } }

        /// <summary>
        /// Uloží layout do registru, do folder a name uvedených v parametrech metody Load()
        /// </summary>
        internal void Save()
        {
            if (!this.LayoutPrepared)
                Throw.SysError(MessageInfo.Get("Panellayout.Save(): missing value for Folder and/or Name."));

            string folder = this.RegFolder;
            string name = this.RegName;

            Steward.WinRegWriteString(folder, name + "_WS", this.MainFormWindowState);
            Steward.WinRegWriteString(folder, name + "_WB", ComponentLibrary.RectangleSerialize(this.MainFormBounds));

            Steward.WinRegWriteInt32(folder, name + "_DC", this.ColumnWidthList.Count);                 // V položce "Name_DC" je počet sloupců
            for (int c = 0; c < this.ColumnWidthList.Count; c++)
                Steward.WinRegWriteInt32(folder, name + "_D" + c.ToString(), this.ColumnWidthList[c]);  // V položkách "Name_D×" (kde × je index sloupce) jsou šířky sloupců

            Steward.WinRegWriteInt32(folder, name + "_PC", this.Pages.Count);                           // V položce "Name_PC" je počet stránek
            foreach (var pageKvp in this.Pages)
                pageKvp.Value.Save(folder, name, pageKvp.Key);                                   // Stránky jsou v položkách "Name_PC" (počet stránek), "Name_P0C" (počet grafů na straně 0), "Name_P0AT" (Top grafu A na straně 0), atd...
        }
        #endregion
        #region ACCEPT LAYOUT (z dat do controlů)
        /// <summary>
        /// Nastaví pozici main okna podle this layoutu
        /// </summary>
        /// <param name="form"></param>
        internal void LayoutAcceptForm(Form form)
        {
            if (form != null)
            {
                string ws = (String.IsNullOrEmpty(this.MainFormWindowState) ? "" : this.MainFormWindowState.ToUpper().Trim());
                if (ws == "S" && !this.MainFormBounds.IsEmpty)
                {   // Stav je "Standardní" a rozměry jsou známy:
                    form.WindowState = FormWindowState.Normal;
                    form.Bounds = this.MainFormBounds;
                }
                else if (ws == "M" && !this.MainFormBounds.IsEmpty)
                {   // Stav je Max, a známe Bounds = to nás může odnést na secondary monitor !
                    form.WindowState = FormWindowState.Maximized;
                    form.Bounds = this.MainFormBounds;
                }
                else
                {   // Jinak: maximized na primárním monitoru:
                    form.WindowState = FormWindowState.Maximized;
                    form.Size = SystemInformation.PrimaryMonitorMaximizedWindowSize;   // Dojde k dopočtení rozměrů grafu i časové osy
                }
            }
        }
        /// <summary>
        /// Umístí určený graf do prostoru jeho parenta s přihlédnutím k aktuálnímu layoutu a pozici okolních grafů.
        /// </summary>
        /// <param name="graphTarget">Graf, do kterého se mají akceptovat prvky layoutu</param>
        /// <param name="items">Položky k LayoutAccept</param>
        /// <param name="sourceTop"></param>
        /// <param name="sourceBottom"></param>
        internal void LayoutAcceptGraph(DColGrid graphTarget, LayoutAcceptItem items, DColGrid sourceTop, DColGrid sourceBottom)
        {
            if (graphTarget == null) return;
            if (!this.CurrentPageExists) return;
            GraphMultiplexPart graphPart = graphTarget.MultiplexPart;
            bool interactive = ((items & LayoutAcceptItem.Interactive) != 0);

            // Layout aktuálního grafu (graphTarget):
            GridPanelLayoutGraph graphLayout = this.CurrentPage.GetGraph(graphPart, false);

            // Bounds celého grafu:
            if ((items & LayoutAcceptItem.GraphBounds) != 0)
            {
                Size parentSize = graphTarget.Parent.ClientSize;
                int x = 0;
                int y = (graphPart == GraphMultiplexPart.PartA ? 0 : (graphPart == GraphMultiplexPart.PartB && this.CurrentGraphBExists ? this.CurrentGraphB.HeaderTop : graphTarget.Top));
                int w = parentSize.Width;
                int b = (graphPart == GraphMultiplexPart.PartA && this.CurrentGraphBExists ? this.CurrentGraphB.HeaderTop : parentSize.Height);
                Rectangle bounds = new Rectangle(x, y, w, (b - y));
                if (!Rectangle.Equals(graphTarget.Bounds, bounds))
                    graphTarget.Bounds = bounds;
            }

            // ColumnWidth:
            if ((items & LayoutAcceptItem.ColumnWidths) != 0)
                graphTarget.Header.ColumnWidthImportFrom(this.ColumnWidthList, interactive);

            // HeaderHeight:
            if ((items & LayoutAcceptItem.HeaderHeight) != 0)
                graphTarget.Header.ColumnHeaderHeightImportFrom(graphLayout, interactive);

            // Graf má nyní najednou akceptovat vše (provést si Arrange Header/Grid a přenést ColumnWidth z Header do Gridu):
            graphTarget.LayoutAccept();
        }
        /// <summary>
        /// true, pokud existuje aktuální stránka
        /// </summary>
        internal bool CurrentPageExists { get { return (this.CurrentPageIndex >= 0 && this.CurrentPageIndex < this.PagesCount); } }
        /// <summary>
        /// Aktuálně platná stránka layoutu. Anebo null.
        /// </summary>
        internal GridPanelLayoutPage CurrentPage { get { return _GetCurrentPage(); } }
        /// <summary>
        /// true, pokud existuje aktuální stránka a na ní existuje graf A
        /// </summary>
        internal bool CurrentGraphAExists { get { return (this.CurrentPageExists && this.CurrentPage.GraphA != null); } }
        /// <summary>
        /// Aktuální graf A, anebo null
        /// </summary>
        internal GridPanelLayoutGraph CurrentGraphA { get { return (this.CurrentGraphAExists ? this.CurrentPage.GraphA : null); } }
        /// <summary>
        /// true, pokud existuje aktuální stránka a na ní existuje graf B
        /// </summary>
        internal bool CurrentGraphBExists { get { return (this.CurrentPageExists && this.CurrentPage.GraphB != null); } }
        /// <summary>
        /// Aktuální graf B, anebo null
        /// </summary>
        internal GridPanelLayoutGraph CurrentGraphB { get { return (this.CurrentGraphBExists ? this.CurrentPage.GraphB : null); } }
        internal void ImportColumnWidths(List<int> list, bool interactive)
        {
            this.ColumnWidthList = new List<int>(list);
        }
        /// <summary>
        /// Do Layoutu uloží vlastnosti Formu
        /// </summary>
        internal void LayoutStoreForm(Form form)
        {
            if (form != null)
            {
                switch (form.WindowState)
                {
                    case FormWindowState.Maximized:
                        this.MainFormWindowState = "M";
                        this.MainFormBounds = form.Bounds;
                        break;
                    case FormWindowState.Normal:
                        this.MainFormWindowState = "S";
                        this.MainFormBounds = form.Bounds;
                        break;
                    // Pokud je okno minimalizované (když se ukončuje pravou myší na taskbaru), pak nebudu ukládat nic, nechám to podle posledního stavu.
                }
            }
        }
		#endregion
		#region VERIFIKACE PO NAČTENÍ
		/// <summary>
        /// Ověří, zda layout obsahuje potřebný počet šířek sloupců, případně chybějící doplní
        /// </summary>
        /// <param name="widths"></param>
        internal void VerifyWidths(params int[] widths)
        {
            if (this.ColumnWidthList == null) this.ColumnWidthList = new List<int>();
            for (int i = 0; i < widths.Length; i++)
            {
                if (this.ColumnWidthList.Count < (i + 1))
                    this.ColumnWidthList.Add(widths[i]);
            }
        }
        /// <summary>
        ///  Ověří, zda layout obsahuje potřebný pro daný index stránky graf, a ověří jeho hodnoty.
        /// </summary>
        /// <param name="pageIndex"></param>
        /// <param name="part"></param>
        /// <param name="top"></param>
        /// <param name="height"></param>
        /// <param name="heigthFilter"></param>
        internal void VerifyGraph(int pageIndex, GraphMultiplexPart part, int minTop, int top, int height, int heigthFilter)
        {
            GridPanelLayoutPage page;
            if (!this.Pages.TryGetValue(pageIndex, out page))
            {
                page = new GridPanelLayoutPage(pageIndex);
                this.Pages.Add(pageIndex, page);
            }
            GridPanelLayoutGraph graph = page.GetGraph(part, true);
            graph.VerifyGraph(minTop, top, height, heigthFilter);
        }
        #endregion
    }
    /// <summary>
    /// Layout jedné stránky multiplexu: info o typu rozložení (1 graf / 2 grafy) + 2 položky rozložení pro graf A a B
    /// </summary>
    internal class GridPanelLayoutPage
    {
        public GridPanelLayoutPage(int pageIndex)
        {
            this.PageIndex = pageIndex;
        }
        internal int PageIndex { get; private set; }
        /// <summary>
        /// Počet grafů (0/1/2)
        /// </summary>
        internal int GrafCount { get { return (GraphA == null ? 0 : 1) + (GraphB == null ? 0 : 1); } }
        /// <summary>
        /// Layout grafu A
        /// </summary>
        internal GridPanelLayoutGraph GraphA { get; set; }
        /// <summary>
        /// Layout grafu B
        /// </summary>
        internal GridPanelLayoutGraph GraphB { get; set; }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "PageIndex=" + this.PageIndex.ToString() +
                "; GraphA=" + (this.GraphA == null ? "null" : this.GraphA.ToString()) +
                "; GraphB=" + (this.GraphB == null ? "null" : this.GraphB.ToString());
        }
        /// <summary>
        /// true, když tato položka má význam
        /// </summary>
        internal bool IsEmpty
        {
            get { return (this.PageIndex < 0 || (this.GraphA == null && this.GraphB == null)); }
        }
		/// <summary>
		/// Uloží data této stránky do WinReg
		/// </summary>
		/// <param name="folder"></param>
		/// <param name="name"></param>
		/// <param name="index"></param>
        internal void Save(string folder, string name, int index)
        {
            string pName = name + "_P" + index.ToString();
            Steward.WinRegWriteInt32(folder, pName + "I", this.PageIndex);            // jméno v registru = "Name_P1I"   (hodnota = index stránky)
            Steward.WinRegWriteInt32(folder, pName + "C", this.GrafCount);            // jméno v registru = "Name_P1C"   (1 = index stránky)
            if (GraphA != null)
                GraphA.Save(folder, pName, "A");
            if (GraphB != null)
                GraphB.Save(folder, pName, "B");
        }
		/// <summary>
		/// Načte data této stránky z WinReg
		/// </summary>
		/// <param name="folder"></param>
		/// <param name="name"></param>
		/// <param name="index"></param>
        internal void Load(string folder, string name, int index)
        {
            string pName = name + "_P" + index.ToString();
            // this.PageIndex = WinReg.ReadInt32(folder, pName + "I", -1);
            int gc = Steward.WinRegReadInt32(folder, pName + "C", 0);
            if (gc > 0)
            {
                this.GraphA = new GridPanelLayoutGraph(GraphMultiplexPart.PartA);
                this.GraphA.Load(folder, pName, "A");
            }
            if (gc > 1)
            {
                this.GraphB = new GridPanelLayoutGraph(GraphMultiplexPart.PartB);
                this.GraphB.Load(folder, pName, "B");
            }
        }
        /// <summary>
        /// Vrátí graf A nebo B, a pokud je null a je povoleno vytvoření, tak jej nejprve vytvoří (a do this zapamatuje).
        /// </summary>
        /// <param name="part"></param>
        /// <param name="createEmpty"></param>
        /// <returns></returns>
        internal GridPanelLayoutGraph GetGraph(GraphMultiplexPart part, bool createEmpty)
        {
            switch (part)
            {
                case GraphMultiplexPart.PartA:
                    if (this.GraphA == null && createEmpty)
                        this.GraphA = new GridPanelLayoutGraph(GraphMultiplexPart.PartA);
                    return this.GraphA;
                case GraphMultiplexPart.PartB:
                    if (this.GraphB == null && createEmpty)
						this.GraphB = new GridPanelLayoutGraph(GraphMultiplexPart.PartB);
                    return this.GraphB;
            }
            return null;
        }
    }
	/// <summary>
	/// GridPanelLayoutGraph : data o pozicích jedné části multiplexu (odpovídá jednomu grafu na záložce, A/B)
	/// </summary>
    internal class GridPanelLayoutGraph
    {
		public GridPanelLayoutGraph(GraphMultiplexPart part)
		{
			this.Part = part;
		}
		/// <summary>
		/// Část multiplexu (A/B).
		/// </summary>
		internal GraphMultiplexPart Part { get; private set; }
        /// <summary>
        /// Pozice Header.Top  (u grafu A je vždy == 0, HeaderA nelze přesouvat, u grafu B představuje pozici Top)
        /// </summary>
        internal int HeaderTop { get; set; }
        /// <summary>
        /// Rozměr Header.Height
        /// </summary>
        internal int HeaderHeight { get; set; }
        /// <summary>
        /// Výška řádkového filtru v Header
        /// </summary>
        internal int HeaderFilterHeight { get; set; }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Part=" + this.Part.ToString() +
                "; Top=" + this.HeaderTop.ToString() +
                "; Height=" + this.HeaderHeight.ToString();
        }
        /// <summary>
        /// Uloží svoje data do registru
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="name"></param>
        /// <param name="graf"></param>
        internal void Save(string folder, string name, string graf)
        {
            string gName = name + graf;
            Steward.WinRegWriteInt32(folder, gName + "T", this.HeaderTop);            // jméno v registru = "Name_P1AT"   (A = písmeno grafu A/B,   T = Top)
            Steward.WinRegWriteInt32(folder, gName + "H", this.HeaderHeight);
            Steward.WinRegWriteInt32(folder, gName + "F", this.HeaderFilterHeight);
        }
        /// <summary>
        /// Načte svoje data z registru
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="name"></param>
        /// <param name="graf"></param>
        internal void Load(string folder, string name, string graf)
        {
            string gName = name + graf;
            this.HeaderTop = Steward.WinRegReadInt32(folder, gName + "T", -1);
            this.HeaderHeight = Steward.WinRegReadInt32(folder, gName + "H", -1);
            this.HeaderFilterHeight = Steward.WinRegReadInt32(folder, gName + "F", -1);
        }
        /// <summary>
        ///  Ověří, zda layout obsahuje potřebný pro daný index stránky graf, a ověří jeho hodnoty.
        /// </summary>
		/// <param name="minTop"></param>
        /// <param name="top"></param>
        /// <param name="height"></param>
        /// <param name="heigthFilter"></param>
        internal void VerifyGraph(int minTop, int top, int height, int heigthFilter)
        {
            if (this.HeaderTop < minTop) this.HeaderTop = top;
            if (this.HeaderHeight < 10) this.HeaderHeight = height;
            if (this.HeaderFilterHeight < 10) this.HeaderFilterHeight = heigthFilter;
        }
    }
    #endregion
    #region DELEGATE MultiplexPageChangeEventHandler, CLASS MultiplexPageChangeEventArgs, ENUM GraphMultiplexPart, LayoutAcceptItem
    internal delegate void MultiplexPageChangeEventHandler(object sender, MultiplexPageChangeEventArgs args);
	/// <summary>
	/// Argumenty, které popisují aktuální stránku a graf
	/// </summary>
	internal class MultiplexPageChangeEventArgs : EventArgs
	{
		internal MultiplexPageChangeEventArgs(int pageIndex, GraphMultiplexItemCls currentPage, DColGrid currentGraph)
		{
			this.PageIndex = pageIndex;
			this.CurrentPage = currentPage;
			this.CurrentGraph = currentGraph;
			this.Cancel = false;
		}
		/// <summary>Index nynější stránky</summary>
		internal int PageIndex { get; private set; }
		/// <summary>Reference na právě aktivní stránku grafu (ta, která má focus)</summary>
		internal GraphMultiplexItemCls CurrentPage { get; private set; }
		/// <summary>Reference na právě aktivní graf (ten, který má focus)</summary>
		internal DColGrid CurrentGraph { get; private set; }
		/// <summary>Stornovat změnu</summary>
		internal bool Cancel { get; set; }
	}
    /// <summary>
    /// Identifikace části multiplexu: graf A / graf B
    /// </summary>
    internal enum GraphMultiplexPart
    {
        None = 1,
        PartA,
        PartB
    }
    /// <summary>
    /// Přehled prvků, které má metoda LayoutAccept přenášet z Layout do controlů
    /// </summary>
    [Flags]
    internal enum LayoutAcceptItem
    {
        None = 0,
        GraphBounds = 1,
        HeaderHeight = 2,
        ColumnWidths = 4,
        MainForm = 0x100,
        Interactive = 0x200
    }
	#endregion
}
